1 /*
   2  * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.shape;
  27 
  28 import com.sun.javafx.scene.shape.ObservableFaceArrayImpl;
  29 import com.sun.javafx.collections.FloatArraySyncer;
  30 import com.sun.javafx.collections.IntegerArraySyncer;
  31 import com.sun.javafx.geom.BaseBounds;
  32 import com.sun.javafx.geom.BoxBounds;
  33 import com.sun.javafx.geom.PickRay;
  34 import com.sun.javafx.geom.Vec3d;
  35 import com.sun.javafx.scene.input.PickResultChooser;
  36 import com.sun.javafx.scene.shape.TriangleMeshHelper;
  37 import com.sun.javafx.sg.prism.NGTriangleMesh;
  38 import javafx.beans.property.ObjectProperty;
  39 import javafx.beans.property.SimpleObjectProperty;
  40 import javafx.collections.ArrayChangeListener;
  41 import javafx.collections.FXCollections;
  42 import javafx.collections.ObservableArray;
  43 import javafx.collections.ObservableFloatArray;
  44 import javafx.collections.ObservableIntegerArray;
  45 import javafx.geometry.Point2D;
  46 import javafx.geometry.Point3D;
  47 import javafx.scene.Node;
  48 import javafx.scene.input.PickResult;
  49 import javafx.scene.transform.Affine;
  50 import javafx.scene.transform.NonInvertibleTransformException;
  51 import javafx.scene.transform.Rotate;
  52 import com.sun.javafx.logging.PlatformLogger;
  53 
  54 /**
  55  * Defines a 3D triangle mesh that consists of its associated {@code VertexFormat}
  56  * and a set of separate arrays of vertex components such as points, normals,
  57  * texture coordinates, and an array of faces that define the individual triangles
  58  * of the mesh.
  59  *<p>
  60  * Note that the term point, as used in the method names and method
  61  * descriptions, actually refers to a 3D point (x, y, z) in space
  62  * representing the position of a single vertex. The term points (plural) is
  63  * used to indicate sets of 3D points for multiple vertices.
  64  * Similarly, the term normal is used to indicate a 3D vector (nx, ny, nz) in space
  65  * representing the direction of a single vertex. The term normals (plural) is
  66  * used to indicate sets of 3D vectors for multiple vertices.
  67  * The term texCoord is used to indicate a single pair of 2D texture
  68  * coordinates (u, v) for a single vertex, while the term texCoords (plural) is used
  69  * to indicate sets of texture coordinates for multiple vertices.
  70  * Lastly, the term face is used to indicate 3 sets of interleaving points,
  71  * normals (optional, depending on the associated VertexFormat)
  72  * and texture coordinates that together represent the geometric topology of a
  73  * single triangle, while the term faces (plural) is used to indicate sets of
  74  * triangles (each represent by a face).
  75  * <p>
  76  * For example, the faces with {@code VertexFormat.POINT_TEXCOORD} that represent
  77  * a single textured rectangle, using 2 triangles, have the following data order: [
  78  * <p>
  79  * p0, t0, p1, t1, p3, t3,  // First triangle of a textured rectangle
  80  * <p>
  81  * p1, t1, p2, t2, p3, t3   // Second triangle of a textured rectangle
  82  * <p>
  83  * ]
  84  * <p>
  85  * whereas the faces with {@code VertexFormat.POINT_NORMAL_TEXCOORD} that represent
  86  * a single textured rectangle, using 2 triangles, have the following data order: [
  87  * <p>
  88  * p0, n0, t0, p1, n1, t1, p3, n3, t3,  // First triangle of a textured rectangle
  89  * <p>
  90  * p1, n1, t1, p2, n2, t2, p3, n3, t3   // Second triangle of a textured rectangle
  91  * <p>
  92  * ]
  93  * <p>
  94  * where p0, p1, p2 and p3 are indices into the points array, n0, n1, n2 and n3
  95  * are indices into the normals array, and t0, t1, t2 and t3 are indices into
  96  * the texCoords array.
  97  *
  98  * <p>
  99  * A triangle has a front and back face. The winding order of a triangle's vertices
 100  * determines which side is the front face. JavaFX chooses the counter-clockwise
 101  * (or right-hand rule) winding order as the front face. By default, only the
 102  * front face of a triangle is rendered. See {@code CullFace} for more
 103  * information.
 104  *
 105  * <p>
 106  * The length of {@code points}, {@code normals}, and {@code texCoords} must be
 107  * divisible by 3, 3, and 2 respectively. The length of {@code faces} must be
 108  * divisible by 6 if it is of {@code VertexFormat.POINT_TEXCOORD} else it must
 109  * be divisible by 9 if it is of {@code VertexFormat.POINT_NORMAL_TEXCOORD}.
 110  * The values in the faces array must be within the range of the number of vertices
 111  * in the points array (0 to points.length / 3 - 1) for the point indices, within
 112  * the range of the number of vertices in the normals array
 113  * (0 to normals.length / 3 - 1) for the normal indices, and within the range of
 114  * the number of the vertices in the texCoords array (0 to texCoords.length / 2 - 1)
 115  * for the texture coordinate indices.
 116  *
 117  * <p> A warning will be recorded to the logger and the mesh will not be rendered
 118  * (and will have an empty bounds) if any of the array lengths are invalid
 119  * or if any of the values in the faces array are out of range.
 120  *
 121  * @since JavaFX 8.0
 122  */
 123 public class TriangleMesh extends Mesh {
 124     static {
 125         TriangleMeshHelper.setTriangleMeshAccessor(new TriangleMeshHelper.TriangleMeshAccessor() {
 126             @Override
 127             public boolean doComputeIntersects(Mesh mesh, PickRay pickRay,
 128                     PickResultChooser pickResult, Node candidate, CullFace cullFace,
 129                     boolean reportFace) {
 130                 return ((TriangleMesh) mesh).doComputeIntersects(pickRay,
 131                         pickResult, candidate, cullFace, reportFace);
 132             }
 133         });
 134     }
 135 
 136     private final ObservableFloatArray points = FXCollections.observableFloatArray();
 137     private final ObservableFloatArray normals = FXCollections.observableFloatArray();
 138     private final ObservableFloatArray texCoords = FXCollections.observableFloatArray();
 139     private final ObservableFaceArray faces = new ObservableFaceArrayImpl();
 140     private final ObservableIntegerArray faceSmoothingGroups = FXCollections.observableIntegerArray();
 141 
 142     private final Listener pointsSyncer = new Listener(points);
 143     private final Listener normalsSyncer = new Listener(normals);
 144     private final Listener texCoordsSyncer = new Listener(texCoords);
 145     private final Listener facesSyncer = new Listener(faces);
 146     private final Listener faceSmoothingGroupsSyncer = new Listener(faceSmoothingGroups);
 147     private final boolean isPredefinedShape;
 148     private boolean isValidDirty = true;
 149     private boolean isPointsValid, isNormalsValid, isTexCoordsValid, isFacesValid, isFaceSmoothingGroupValid;
 150     private int refCount = 1;
 151 
 152     private BaseBounds cachedBounds;
 153 
 154     /**
 155      * Creates a new instance of {@code TriangleMesh} class with the default
 156      * {@code VertexFormat.POINT_TEXCOORD} format type.
 157      */
 158     public TriangleMesh() {
 159         this(false);
 160         TriangleMeshHelper.initHelper(this);
 161     }
 162 
 163     /**
 164      * Creates a new instance of {@code TriangleMesh} class with the specified
 165      * {@code VertexFormat}.
 166      *
 167      * @param vertexFormat specifies the vertex format type.
 168      *
 169      * @since JavaFX 8u40
 170      */
 171     public TriangleMesh(VertexFormat vertexFormat) {
 172         this(false);
 173         this.setVertexFormat(vertexFormat);
 174         TriangleMeshHelper.initHelper(this);
 175     }
 176 
 177     TriangleMesh(boolean isPredefinedShape) {
 178         this.isPredefinedShape = isPredefinedShape;
 179         if (isPredefinedShape) {
 180             isPointsValid = true;
 181             isNormalsValid = true;
 182             isTexCoordsValid = true;
 183             isFacesValid = true;
 184             isFaceSmoothingGroupValid = true;
 185         } else {
 186             isPointsValid = false;
 187             isNormalsValid = false;
 188             isTexCoordsValid = false;
 189             isFacesValid = false;
 190             isFaceSmoothingGroupValid = false;
 191         }
 192         TriangleMeshHelper.initHelper(this);
 193     }
 194 
 195     /**
 196      * Specifies the vertex format of this {@code TriangleMesh}, one of
 197      * {@code VertexFormat.POINT_TEXCOORD} or {@code VertexFormat.POINT_NORMAL_TEXCOORD}.
 198      *
 199      * @defaultValue VertexFormat.POINT_TEXCOORD
 200      *
 201      * @since JavaFX 8u40
 202      */
 203     private ObjectProperty<VertexFormat> vertexFormat;
 204 
 205     public final void setVertexFormat(VertexFormat value) {
 206         vertexFormatProperty().set(value);
 207     }
 208 
 209     public final VertexFormat getVertexFormat() {
 210         return vertexFormat == null ? VertexFormat.POINT_TEXCOORD : vertexFormat.get();
 211     }
 212 
 213     public final ObjectProperty<VertexFormat> vertexFormatProperty() {
 214         if (vertexFormat == null) {
 215             vertexFormat = new SimpleObjectProperty<VertexFormat>(TriangleMesh.this, "vertexFormat") {
 216 
 217                 @Override
 218                 protected void invalidated() {
 219                     setDirty(true);
 220                     // Need to mark faces and faceSmoothingGroups dirty too.
 221                     facesSyncer.setDirty(true);
 222                     faceSmoothingGroupsSyncer.setDirty(true);
 223                 }
 224             };
 225         }
 226         return vertexFormat;
 227     }
 228 
 229     /**
 230      * Returns the number of elements that represents a point.
 231      *
 232      * @return number of elements
 233      */
 234     public final int getPointElementSize() {
 235         return getVertexFormat().getPointElementSize();
 236     }
 237 
 238     /**
 239      * Returns the number of elements that represents a normal.
 240      *
 241      * @return number of elements
 242      *
 243      * @since JavaFX 8u40
 244      */
 245     public final int getNormalElementSize() {
 246         return getVertexFormat().getNormalElementSize();
 247     }
 248 
 249     /**
 250      * Returns the number of elements that represents a texture coordinates.
 251      *
 252      * @return number of elements
 253      */
 254     public final int getTexCoordElementSize() {
 255         return getVertexFormat().getTexCoordElementSize();
 256     }
 257 
 258     /**
 259      * Returns the number of elements that represents a face.
 260      *
 261      * @return number of elements
 262      */
 263     public final int getFaceElementSize() {
 264         return getVertexFormat().getVertexIndexSize() * 3;
 265     }
 266 
 267     /**
 268      * Gets the {@code points} array of this {@code TriangleMesh}.
 269      *
 270      * @return {@code points} array where each point is
 271      * represented by 3 float values x, y and z, in that order.
 272      */
 273     public final ObservableFloatArray getPoints() {
 274         return points;
 275     }
 276 
 277     /**
 278      * Gets the {@code normals} array of this {@code TriangleMesh}.
 279      *
 280      * @return {@code normals} array where each normal is
 281      * represented by 3 float values nx, ny and nz, in that order.
 282      *
 283      * @since JavaFX 8u40
 284      */
 285     public final ObservableFloatArray getNormals() {
 286         return normals;
 287     }
 288 
 289     /**
 290      * Gets the  {@code texCoords} array of this {@code TriangleMesh}.
 291      * The coordinates are proportional, so texture's top-left corner
 292      * is at [0, 0] and bottom-right corner is at [1, 1].
 293      *
 294      * @return {@code texCoord} array where each texture coordinate is represented
 295      * by 2 float values: u and v, in that order.
 296      */
 297     public final ObservableFloatArray getTexCoords() {
 298         return texCoords;
 299     }
 300 
 301     /**
 302      * Gets the {@code faces} array, indices into the {@code points},
 303      * {@code normals} (optional, if it is a {@code VertexFormat.POINT_NORMAL_TEXCOORD}
 304      * mesh) and {@code texCoords} arrays, of this  {@code TriangleMesh}. All
 305      * indices are in terms of elements in to the points, normals or texCoords
 306      * arrays not individual floats.
 307      *
 308      * @return {@code faces} array where each face is of
 309      * 3 * {@code VertexFormat.getVertexIndexSize()} integers.
 310      */
 311     public final ObservableFaceArray getFaces() {
 312         return faces;
 313     }
 314 
 315     /**
 316      * Gets the {@code faceSmoothingGroups} array of this {@code TriangleMesh}.
 317      * Smoothing affects how a mesh is rendered but it does not effect its
 318      * geometry. The face smoothing group value is used to control the smoothing
 319      * between adjacent faces.
 320      *
 321      * <p>
 322      * {@literal The face smoothing group value is represented by an array of bits and up to
 323      * 32 unique groups is possible; (1 << 0) to (1 << 31). The face smoothing
 324      * group value can range from 0 (no smoothing group) to all 32 groups. A face
 325      * can belong to zero or more smoothing groups. A face is a member of group
 326      * N if bit N is set, for example, groups |= (1 << N). A value of 0 implies
 327      * no smoothing group or hard edges.}
 328      * Smoothing is applied when adjacent pair of faces shared a smoothing group.
 329      * Otherwise the faces are rendered with a hard edge between them.
 330      *
 331      * <p> An empty faceSmoothingGroups implies all faces in this mesh have a
 332      * smoothing group value of 1.
 333      *
 334      * <p> If faceSmoothingGroups is not empty, its size must
 335      * be equal to number of faces.
 336      *
 337      * <p> This faceSmoothingGroups has no effect on its {@code TriangleMesh} if
 338      * it is of {@code VertexFormat.POINT_NORMAL_TEXCOORD} format.
 339      * @return the {@code faceSmoothingGroups} array of this {@code TriangleMesh}
 340      */
 341     public final ObservableIntegerArray getFaceSmoothingGroups() {
 342         return faceSmoothingGroups;
 343     }
 344 
 345     @Override void setDirty(boolean value) {
 346         super.setDirty(value);
 347         if (!value) { // false
 348             pointsSyncer.setDirty(false);
 349             normalsSyncer.setDirty(false);
 350             texCoordsSyncer.setDirty(false);
 351             facesSyncer.setDirty(false);
 352             faceSmoothingGroupsSyncer.setDirty(false);
 353         }
 354     }
 355 
 356     int getRefCount() {
 357         return refCount;
 358     }
 359 
 360     synchronized void incRef() {
 361         this.refCount += 1;
 362     }
 363 
 364     synchronized void decRef() {
 365         this.refCount -= 1;
 366     }
 367 
 368     private NGTriangleMesh peer;
 369 
 370     /* The peer node created by the graphics Toolkit/Pipeline implementation */
 371     NGTriangleMesh getPGTriangleMesh() {
 372         if (peer == null) {
 373             peer = new NGTriangleMesh();
 374         }
 375         return peer;
 376     }
 377 
 378     @Override
 379     NGTriangleMesh getPGMesh() {
 380         return getPGTriangleMesh();
 381     }
 382 
 383     private boolean validatePoints() {
 384         if (points.size() == 0) { // Valid but meaningless for picking or rendering.
 385             return false;
 386         }
 387 
 388         if ((points.size() % getVertexFormat().getPointElementSize()) != 0) {
 389             String logname = TriangleMesh.class.getName();
 390             PlatformLogger.getLogger(logname).warning("points.size() has "
 391                     + "to be divisible by getPointElementSize(). It is to"
 392                     + " store multiple x, y, and z coordinates of this mesh");
 393             return false;
 394         }
 395         return true;
 396     }
 397 
 398     private boolean validateNormals() {
 399         // Only validate normals if vertex format has normal component
 400         if (getVertexFormat() != VertexFormat.POINT_NORMAL_TEXCOORD) return true;
 401 
 402         if (normals.size() == 0) { // Valid but meaningless for picking or rendering.
 403             return false;
 404         }
 405 
 406         if ((normals.size() % getVertexFormat().getNormalElementSize()) != 0) {
 407             String logname = TriangleMesh.class.getName();
 408             PlatformLogger.getLogger(logname).warning("normals.size() has "
 409                     + "to be divisible by getNormalElementSize(). It is to"
 410                     + " store multiple nx, ny, and nz coordinates of this mesh");
 411             return false;
 412         }
 413         return true;
 414     }
 415 
 416     private boolean validateTexCoords() {
 417         if (texCoords.size() == 0) { // Valid but meaningless for picking or rendering.
 418             return false;
 419         }
 420 
 421         if ((texCoords.size() % getVertexFormat().getTexCoordElementSize()) != 0) {
 422             String logname = TriangleMesh.class.getName();
 423             PlatformLogger.getLogger(logname).warning("texCoords.size() "
 424                     + "has to be divisible by getTexCoordElementSize()."
 425                     + " It is to store multiple u and v texture coordinates"
 426                     + " of this mesh");
 427             return false;
 428         }
 429         return true;
 430     }
 431 
 432     private boolean validateFaces() {
 433         if (faces.size() == 0) { // Valid but meaningless for picking or rendering.
 434             return false;
 435         }
 436 
 437         String logname = TriangleMesh.class.getName();
 438         if ((faces.size() % getFaceElementSize()) != 0) {
 439             PlatformLogger.getLogger(logname).warning("faces.size() has "
 440                     + "to be divisible by getFaceElementSize().");
 441             return false;
 442         }
 443 
 444         if (getVertexFormat() == VertexFormat.POINT_TEXCOORD) {
 445             int nVerts = points.size() / getVertexFormat().getPointElementSize();
 446             int nTVerts = texCoords.size() / getVertexFormat().getTexCoordElementSize();
 447             for (int i = 0; i < faces.size(); i++) {
 448                 if (i % 2 == 0 && (faces.get(i) >= nVerts || faces.get(i) < 0)
 449                         || (i % 2 != 0 && (faces.get(i) >= nTVerts || faces.get(i) < 0))) {
 450                     PlatformLogger.getLogger(logname).warning("The values in the "
 451                             + "faces array must be within the range of the number "
 452                             + "of vertices in the points array (0 to points.length / 3 - 1) "
 453                             + "for the point indices and within the range of the "
 454                             + "number of the vertices in the texCoords array (0 to "
 455                             + "texCoords.length / 2 - 1) for the texture coordinate indices.");
 456                     return false;
 457                 }
 458             }
 459         } else if (getVertexFormat() == VertexFormat.POINT_NORMAL_TEXCOORD) {
 460             int nVerts = points.size() / getVertexFormat().getPointElementSize();
 461             int nNVerts =  normals.size() / getVertexFormat().getNormalElementSize();
 462             int nTVerts = texCoords.size() / getVertexFormat().getTexCoordElementSize();
 463             for (int i = 0; i < faces.size(); i+=3) {
 464                 if ((faces.get(i) >= nVerts || faces.get(i) < 0)
 465                         || (faces.get(i + 1) >= nNVerts || faces.get(i + 1) < 0)
 466                         || (faces.get(i + 2) >= nTVerts || faces.get(i + 2) < 0)) {
 467                     PlatformLogger.getLogger(logname).warning("The values in the "
 468                             + "faces array must be within the range of the number "
 469                             + "of vertices in the points array (0 to points.length / 3 - 1) "
 470                             + "for the point indices, and within the range of the "
 471                             + "number of the vertices in the normals array (0 to "
 472                             + "normals.length / 3 - 1) for the normals indices, and "
 473                             + "number of the vertices in the texCoords array (0 to "
 474                             + "texCoords.length / 2 - 1) for the texture coordinate indices.");
 475                     return false;
 476                 }
 477             }
 478         } else {
 479             PlatformLogger.getLogger(logname).warning("Unsupported VertexFormat: " + getVertexFormat().toString());
 480             return false;
 481         }
 482         return true;
 483     }
 484 
 485     private boolean validateFaceSmoothingGroups() {
 486         if (faceSmoothingGroups.size() != 0
 487                 && faceSmoothingGroups.size() != (faces.size() / getFaceElementSize())) {
 488             String logname = TriangleMesh.class.getName();
 489             PlatformLogger.getLogger(logname).warning("faceSmoothingGroups.size()"
 490                     + " has to equal to number of faces.");
 491             return false;
 492         }
 493         return true;
 494     }
 495 
 496     private boolean validate() {
 497         if (isPredefinedShape) {
 498             return true;
 499         }
 500 
 501         if (isValidDirty) {
 502             if (pointsSyncer.dirtyInFull) {
 503                 isPointsValid = validatePoints();
 504             }
 505             if (normalsSyncer.dirtyInFull) {
 506                 isNormalsValid = validateNormals();
 507             }
 508             if (texCoordsSyncer.dirtyInFull) {
 509                 isTexCoordsValid = validateTexCoords();
 510             }
 511             if (facesSyncer.dirty || pointsSyncer.dirtyInFull
 512                     || normalsSyncer.dirtyInFull || texCoordsSyncer.dirtyInFull) {
 513                 isFacesValid = isPointsValid && isNormalsValid
 514                         && isTexCoordsValid && validateFaces();
 515             }
 516             if (faceSmoothingGroupsSyncer.dirtyInFull || facesSyncer.dirtyInFull) {
 517                 isFaceSmoothingGroupValid = isFacesValid && validateFaceSmoothingGroups();
 518             }
 519             isValidDirty = false;
 520         }
 521         return isPointsValid && isNormalsValid && isTexCoordsValid
 522                 && isFaceSmoothingGroupValid && isFacesValid;
 523     }
 524 
 525     @Override
 526     void updatePG() {
 527         if (!isDirty()) {
 528             return;
 529         }
 530 
 531         final NGTriangleMesh pgTriMesh = getPGTriangleMesh();
 532         if (validate()) {
 533             pgTriMesh.setUserDefinedNormals(getVertexFormat() == VertexFormat.POINT_NORMAL_TEXCOORD);
 534             pgTriMesh.syncPoints(pointsSyncer);
 535             pgTriMesh.syncNormals(normalsSyncer);
 536             pgTriMesh.syncTexCoords(texCoordsSyncer);
 537             pgTriMesh.syncFaces(facesSyncer);
 538             pgTriMesh.syncFaceSmoothingGroups(faceSmoothingGroupsSyncer);
 539         } else {
 540             pgTriMesh.setUserDefinedNormals(false);
 541             pgTriMesh.syncPoints(null);
 542             pgTriMesh.syncNormals(null);
 543             pgTriMesh.syncTexCoords(null);
 544             pgTriMesh.syncFaces(null);
 545             pgTriMesh.syncFaceSmoothingGroups(null);
 546         }
 547         setDirty(false);
 548     }
 549 
 550     @Override
 551     BaseBounds computeBounds(BaseBounds bounds) {
 552         if (isDirty() || cachedBounds == null) {
 553             cachedBounds = new BoxBounds();
 554             if (validate()) {
 555                 final int len = points.size();
 556                 final int pointElementSize = getVertexFormat().getPointElementSize();
 557                 for (int i = 0; i < len; i += pointElementSize) {
 558                     cachedBounds.add(points.get(i), points.get(i + 1), points.get(i + 2));
 559                 }
 560             }
 561         }
 562         return bounds.deriveWithNewBounds(cachedBounds);
 563     }
 564 
 565     /**
 566      * Computes the centroid of the given triangle
 567      * @param v0x x coord of first vertex of the triangle
 568      * @param v0y y coord of first vertex of the triangle
 569      * @param v0z z coord of first vertex of the triangle
 570      * @param v1x x coord of second vertex of the triangle
 571      * @param v1y y coord of second vertex of the triangle
 572      * @param v1z z coord of second vertex of the triangle
 573      * @param v2x x coord of third vertex of the triangle
 574      * @param v2y y coord of third vertex of the triangle
 575      * @param v2z z coord of third vertex of the triangle
 576      * @return the triangle centroid
 577      */
 578     private Point3D computeCentroid(
 579             double v0x, double v0y, double v0z,
 580             double v1x, double v1y, double v1z,
 581             double v2x, double v2y, double v2z) {
 582 
 583 //        Point3D center = v1.midpoint(v2);
 584 //        Point3D vec = center.subtract(v0);
 585 //        return v0.add(new Point3D(vec.getX() / 3.0, vec.getY() / 3.0, vec.getZ() / 3.0));
 586 
 587         return new Point3D(
 588             v0x + (v2x + (v1x - v2x) / 2.0 - v0x) / 3.0,
 589             v0y + (v2y + (v1y - v2y) / 2.0 - v0y) / 3.0,
 590             v0z + (v2z + (v1z - v2z) / 2.0 - v0z) / 3.0);
 591     }
 592 
 593     /**
 594      * Computes the centroid of the given triangle
 595      * @param v0 vertex of the triangle
 596      * @param v1 vertex of the triangle
 597      * @param v2 vertex of the triangle
 598      * @return the triangle centroid
 599      */
 600     private Point2D computeCentroid(Point2D v0, Point2D v1, Point2D v2) {
 601         Point2D center = v1.midpoint(v2);
 602 
 603         Point2D vec = center.subtract(v0);
 604         return v0.add(new Point2D(vec.getX() / 3.0, vec.getY() / 3.0));
 605     }
 606 
 607     /**
 608      * Computes intersection of a pick ray and a single triangle face.
 609      *
 610      * It takes pickRay, origin and dir. The latter two can be of course obtained
 611      * from the pickRay, but we need them to be converted to Point3D and don't
 612      * want to do that for all faces. Therefore the conversion is done just once
 613      * and passed to the method for all the faces.
 614      *
 615      * @param pickRay pick ray
 616      * @param origin pick ray's origin
 617      * @param dir pick ray's direction
 618      * @param faceIndex index of the face to test
 619      * @param cullFace cull face of the Node (and thus the tested face)
 620      * @param candidate the owner node (for the possible placement to the result)
 621      * @param reportFace whether or not to report he hit face
 622      * @param result the pick result to be updated if a closer intersection is found
 623      * @return true if the pick ray intersects with the face (regardless of whether
 624      *              the result has been updated)
 625      */
 626     private boolean computeIntersectsFace(
 627             PickRay pickRay, Vec3d origin, Vec3d dir, int faceIndex,
 628             CullFace cullFace, Node candidate, boolean reportFace, PickResultChooser result) {//, BoxBounds rayBounds) {
 629 
 630         // This computation was naturally done by Point3D and its operations,
 631         // but it needs a lot of points and there is often a lot of triangles
 632         // so it is vital for performance to use only primitive variables
 633         // and do the computing manually.
 634 
 635         int vertexIndexSize = getVertexFormat().getVertexIndexSize();
 636         int pointElementSize = getVertexFormat().getPointElementSize();
 637         final int v0Idx = faces.get(faceIndex) * pointElementSize;
 638         final int v1Idx = faces.get(faceIndex + vertexIndexSize) * pointElementSize;
 639         final int v2Idx = faces.get(faceIndex + (2 * vertexIndexSize)) * pointElementSize;
 640 
 641         final float v0x = points.get(v0Idx);
 642         final float v0y = points.get(v0Idx + 1);
 643         final float v0z = points.get(v0Idx + 2);
 644         final float v1x = points.get(v1Idx);
 645         final float v1y = points.get(v1Idx + 1);
 646         final float v1z = points.get(v1Idx + 2);
 647         final float v2x = points.get(v2Idx);
 648         final float v2y = points.get(v2Idx + 1);
 649         final float v2z = points.get(v2Idx + 2);
 650 
 651         // e1 = v1.subtract(v0)
 652         final float e1x = v1x - v0x;
 653         final float e1y = v1y - v0y;
 654         final float e1z = v1z - v0z;
 655         // e2 = v2.subtract(v0)
 656         final float e2x = v2x - v0x;
 657         final float e2y = v2y - v0y;
 658         final float e2z = v2z - v0z;
 659 
 660         // h = dir.crossProduct(e2)
 661         final double hx = dir.y * e2z - dir.z * e2y;
 662         final double hy = dir.z * e2x - dir.x * e2z;
 663         final double hz = dir.x * e2y - dir.y * e2x;
 664 
 665         // a = e1.dotProduct(h)
 666         final double a = e1x * hx + e1y * hy + e1z * hz;
 667         if (a == 0.0) {
 668             return false;
 669         }
 670         final double f = 1.0 / a;
 671 
 672         // s = origin.subtract(v0)
 673         final double sx = origin.x - v0x;
 674         final double sy = origin.y - v0y;
 675         final double sz = origin.z - v0z;
 676 
 677         // u = f * (s.dotProduct(h))
 678         final double u = f * (sx * hx + sy * hy + sz * hz);
 679 
 680         if (u < 0.0 || u > 1.0) {
 681             return false;
 682         }
 683 
 684         // q = s.crossProduct(e1)
 685         final double qx = sy * e1z - sz * e1y;
 686         final double qy = sz * e1x - sx * e1z;
 687         final double qz = sx * e1y - sy * e1x;
 688 
 689         // v = f * dir.dotProduct(q)
 690         double v = f * (dir.x * qx + dir.y * qy + dir.z * qz);
 691 
 692         if (v < 0.0 || u + v > 1.0) {
 693             return false;
 694         }
 695 
 696         // t = f * e2.dotProduct(q)
 697         final double t = f * (e2x * qx + e2y * qy + e2z * qz);
 698 
 699         if (t >= pickRay.getNearClip() && t <= pickRay.getFarClip()) {
 700             // This branch is entered only for hit triangles (not so often),
 701             // so we can get smoothly back to the nice code using Point3Ds.
 702 
 703             if (cullFace != CullFace.NONE) {
 704                 // normal = e1.crossProduct(e2)
 705                 final Point3D normal = new Point3D(
 706                     e1y * e2z - e1z * e2y,
 707                     e1z * e2x - e1x * e2z,
 708                     e1x * e2y - e1y * e2x);
 709 
 710                 final double nangle = normal.angle(
 711                         new Point3D(-dir.x, -dir.y, -dir.z));
 712                 if ((nangle >= 90 || cullFace != CullFace.BACK) &&
 713                         (nangle <= 90 || cullFace != CullFace.FRONT)) {
 714                     // hit culled face
 715                     return false;
 716                 }
 717             }
 718 
 719             if (Double.isInfinite(t) || Double.isNaN(t)) {
 720                 // we've got a nonsense pick ray or triangle
 721                 return false;
 722             }
 723 
 724             if (result == null || !result.isCloser(t)) {
 725                 // it intersects, but we are not interested in the result
 726                 // or we already have a better (closer) result
 727                 // so we can omit the point and texture computation
 728                 return true;
 729             }
 730 
 731             Point3D point = PickResultChooser.computePoint(pickRay, t);
 732 
 733             // Now compute texture mapping. First rotate the triangle
 734             // so that we can compute in 2D
 735 
 736             // centroid = computeCentroid(v0, v1, v2);
 737             final Point3D centroid = computeCentroid(
 738                     v0x, v0y, v0z,
 739                     v1x, v1y, v1z,
 740                     v2x, v2y, v2z);
 741 
 742             // cv0 = v0.subtract(centroid)
 743             final Point3D cv0 = new Point3D(
 744                     v0x - centroid.getX(),
 745                     v0y - centroid.getY(),
 746                     v0z - centroid.getZ());
 747             // cv1 = v1.subtract(centroid)
 748             final Point3D cv1 = new Point3D(
 749                     v1x - centroid.getX(),
 750                     v1y - centroid.getY(),
 751                     v1z - centroid.getZ());
 752             // cv2 = v2.subtract(centroid)
 753             final Point3D cv2 = new Point3D(
 754                     v2x - centroid.getX(),
 755                     v2y - centroid.getY(),
 756                     v2z - centroid.getZ());
 757 
 758             final Point3D ce1 = cv1.subtract(cv0);
 759             final Point3D ce2 = cv2.subtract(cv0);
 760             Point3D n = ce1.crossProduct(ce2);
 761             if (n.getZ() < 0) {
 762                 n = new Point3D(-n.getX(), -n.getY(), -n.getZ());
 763             }
 764             final Point3D ax = n.crossProduct(Rotate.Z_AXIS);
 765             final double angle = Math.atan2(ax.magnitude(), n.dotProduct(Rotate.Z_AXIS));
 766 
 767             Rotate r = new Rotate(Math.toDegrees(angle), ax);
 768             final Point3D crv0 = r.transform(cv0);
 769             final Point3D crv1 = r.transform(cv1);
 770             final Point3D crv2 = r.transform(cv2);
 771             final Point3D rPoint = r.transform(point.subtract(centroid));
 772 
 773             final Point2D flatV0 = new Point2D(crv0.getX(), crv0.getY());
 774             final Point2D flatV1 = new Point2D(crv1.getX(), crv1.getY());
 775             final Point2D flatV2 = new Point2D(crv2.getX(), crv2.getY());
 776             final Point2D flatPoint = new Point2D(rPoint.getX(), rPoint.getY());
 777 
 778             // Obtain the texture triangle
 779             int texCoordElementSize = getVertexFormat().getTexCoordElementSize();
 780             int texCoordOffset = getVertexFormat().getTexCoordIndexOffset();
 781             final int t0Idx = faces.get(faceIndex + texCoordOffset) * texCoordElementSize;
 782             final int t1Idx = faces.get(faceIndex + vertexIndexSize + texCoordOffset) * texCoordElementSize;
 783             final int t2Idx = faces.get(faceIndex + (vertexIndexSize * 2) + texCoordOffset) * texCoordElementSize;
 784 
 785             final Point2D u0 = new Point2D(texCoords.get(t0Idx), texCoords.get(t0Idx + 1));
 786             final Point2D u1 = new Point2D(texCoords.get(t1Idx), texCoords.get(t1Idx + 1));
 787             final Point2D u2 = new Point2D(texCoords.get(t2Idx), texCoords.get(t2Idx + 1));
 788 
 789             final Point2D txCentroid = computeCentroid(u0, u1, u2);
 790 
 791             final Point2D cu0 = u0.subtract(txCentroid);
 792             final Point2D cu1 = u1.subtract(txCentroid);
 793             final Point2D cu2 = u2.subtract(txCentroid);
 794 
 795             // Find the transform between the two triangles
 796 
 797             final Affine src = new Affine(
 798                     flatV0.getX(), flatV1.getX(), flatV2.getX(),
 799                     flatV0.getY(), flatV1.getY(), flatV2.getY());
 800             final Affine trg = new Affine(
 801                     cu0.getX(), cu1.getX(), cu2.getX(),
 802                     cu0.getY(), cu1.getY(), cu2.getY());
 803 
 804             Point2D txCoords = null;
 805 
 806             try {
 807                 src.invert();
 808                 trg.append(src);
 809                 txCoords = txCentroid.add(trg.transform(flatPoint));
 810             } catch (NonInvertibleTransformException e) {
 811                 // Can't compute texture mapping, probably the coordinates
 812                 // don't make sense. Ignore it and return null tex coords.
 813             }
 814 
 815             result.offer(candidate, t,
 816                     reportFace ? faceIndex / getFaceElementSize() : PickResult.FACE_UNDEFINED,
 817                     point, txCoords);
 818             return true;
 819         }
 820 
 821         return false;
 822     }
 823 
 824     /*
 825      * Note: This method MUST only be called via its accessor method.
 826      */
 827     private boolean doComputeIntersects(PickRay pickRay, PickResultChooser pickResult,
 828             Node candidate, CullFace cullFace, boolean reportFace) {
 829 
 830         boolean found = false;
 831         if (validate()) {
 832             final int size = faces.size();
 833 
 834             final Vec3d o = pickRay.getOriginNoClone();
 835 
 836             final Vec3d d = pickRay.getDirectionNoClone();
 837 
 838             for (int i = 0; i < size; i += getFaceElementSize()) {
 839                 if (computeIntersectsFace(pickRay, o, d, i, cullFace, candidate,
 840                         reportFace, pickResult)) {
 841                     found = true;
 842                 }
 843             }
 844         }
 845         return found;
 846     }
 847 
 848     private class Listener<T extends ObservableArray<T>> implements ArrayChangeListener<T>, FloatArraySyncer, IntegerArraySyncer {
 849 
 850         protected final T array;
 851         protected boolean dirty = true;
 852         /**
 853          * Array was replaced
 854          * @return true if array was replaced; false otherwise
 855          */
 856         protected boolean dirtyInFull = true;
 857         protected int dirtyRangeFrom;
 858         protected int dirtyRangeLength;
 859 
 860         public Listener(T array) {
 861             this.array = array;
 862             array.addListener(this);
 863         }
 864 
 865         /**
 866          * Adds a dirty range
 867          * @param from index of the first modified element
 868          * @param length length of the modified range
 869          */
 870         protected final void addDirtyRange(int from, int length) {
 871             if (length > 0 && !dirtyInFull) {
 872                 markDirty();
 873                 if (dirtyRangeLength == 0) {
 874                     dirtyRangeFrom = from;
 875                     dirtyRangeLength = length;
 876                 } else {
 877                     int fromIndex = Math.min(dirtyRangeFrom, from);
 878                     int toIndex = Math.max(dirtyRangeFrom + dirtyRangeLength, from + length);
 879                     dirtyRangeFrom = fromIndex;
 880                     dirtyRangeLength = toIndex - fromIndex;
 881                 }
 882             }
 883         }
 884 
 885         protected void markDirty() {
 886             dirty = true;
 887             TriangleMesh.this.setDirty(true);
 888         }
 889 
 890         @Override
 891         public void onChanged(T observableArray, boolean sizeChanged, int from, int to) {
 892             if (sizeChanged) {
 893                 setDirty(true);
 894             } else {
 895                 addDirtyRange(from, to - from);
 896             }
 897             isValidDirty = true;
 898         }
 899 
 900         /**
 901          * @param dirty if true, the whole collection is marked as dirty;
 902          * if false, the whole collection is marked as not-dirty
 903          */
 904         public final void setDirty(boolean dirty) {
 905             this.dirtyInFull = dirty;
 906             if (dirty) {
 907                 markDirty();
 908                 dirtyRangeFrom = 0;
 909                 dirtyRangeLength = array.size();
 910             } else {
 911                 this.dirty = false;
 912                 dirtyRangeFrom = dirtyRangeLength = 0;
 913             }
 914         }
 915 
 916         @Override
 917         public float[] syncTo(float[] array, int[] fromAndLengthIndices) {
 918             assert ((fromAndLengthIndices != null) && (fromAndLengthIndices.length == 2));
 919             ObservableFloatArray floatArray = (ObservableFloatArray) this.array;
 920             if (dirtyInFull || array == null || array.length != floatArray.size()) {
 921                 // Always allocate a new array when size changes
 922                 fromAndLengthIndices[0] = 0;
 923                 fromAndLengthIndices[1] = floatArray.size();
 924                 return floatArray.toArray(null);
 925             }
 926             fromAndLengthIndices[0] = dirtyRangeFrom;
 927             fromAndLengthIndices[1] = dirtyRangeLength;
 928             floatArray.copyTo(dirtyRangeFrom, array, dirtyRangeFrom, dirtyRangeLength);
 929             return array;
 930         }
 931 
 932         @Override
 933         public int[] syncTo(int[] array, int[] fromAndLengthIndices) {
 934             assert ((fromAndLengthIndices != null) && (fromAndLengthIndices.length == 2));
 935             ObservableIntegerArray intArray = (ObservableIntegerArray) this.array;
 936             if (dirtyInFull || array == null || array.length != intArray.size()) {
 937                 fromAndLengthIndices[0] = 0;
 938                 fromAndLengthIndices[1] = intArray.size();
 939                 // Always allocate a new array when size changes
 940                 return intArray.toArray(null);
 941             }
 942             fromAndLengthIndices[0] = dirtyRangeFrom;
 943             fromAndLengthIndices[1] = dirtyRangeLength;
 944             intArray.copyTo(dirtyRangeFrom, array, dirtyRangeFrom, dirtyRangeLength);
 945             return array;
 946         }
 947     }
 948 }