1 /* 2 * Copyright (c) 2013, 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.sg.prism.NGTriangleMesh; 37 import javafx.collections.ArrayChangeListener; 38 import javafx.collections.FXCollections; 39 import javafx.collections.ObservableArray; 40 import javafx.collections.ObservableFloatArray; 41 import javafx.collections.ObservableIntegerArray; 42 import javafx.geometry.Point2D; 43 import javafx.geometry.Point3D; 44 import javafx.scene.Node; 45 import javafx.scene.input.PickResult; 46 import javafx.scene.transform.Affine; 47 import javafx.scene.transform.NonInvertibleTransformException; 48 import javafx.scene.transform.Rotate; 49 import sun.util.logging.PlatformLogger; 50 51 /** 52 * Defines a 3D geometric object contains separate arrays of points, 53 * texture coordinates, and faces that describe a triangulated 54 * geometric mesh. 55 *<p> 56 * Note that the term point, as used in the method names and method 57 * descriptions, actually refers to a set of x, y, and z point 58 * representing the position of a single vertex. The term points (plural) is 59 * used to indicate sets of x, y, and z points for multiple vertices. 60 * Similarly, the term texCoord is used to indicate a set of u and v texture 61 * coordinates for a single vertex, while the term texCoords (plural) is used 62 * to indicate sets of u and v texture coordinates for multiple vertices. 63 * Lastly, the term face is used to indicate 3 set of interleaving points 64 * and texture coordinates that together represent the geometric topology of a 65 * single triangle, while the term faces (plural) is used to indicate sets of 66 * triangles (each represent by a face). 67 * <p> 68 * For example, the faces that represent a single textured rectangle, using 2 triangles, 69 * has the following data order: [ 70 * <p> 71 * p0, t0, p1, t1, p3, t3, // First triangle of a textured rectangle 72 * <p> 73 * p1, t1, p2, t2, p3, t3 // Second triangle of a textured rectangle 74 * <p> 75 * ] 76 * <p> 77 * where p0, p1, p2 and p3 are indices into the points array, and t0, t1, t2 78 * and t3 are indices into the texCoords array. 79 * 80 * <p> 81 * A triangle has a front and back face. The winding order of a triangle's vertices 82 * determines which side is the front face. JavaFX chooses the counter-clockwise 83 * (or right-hand rule) winding order as the front face. By default, only the 84 * front face of a triangle is rendered. See {@code CullFace} for more 85 * information. 86 * 87 * <p> 88 * The length of {@code points}, {@code texCoords}, and {@code faces} must be 89 * divisible by 3, 2, and 6 respectively. 90 * The values in the faces array must be within the range of the number of vertices 91 * in the points array (0 to points.length / 3 - 1) for the point indices and 92 * within the range of the number of the vertices in 93 * the texCoords array (0 to texCoords.length / 2 - 1) for the texture coordinate indices. 94 * 95 * <p> A warning will be recorded to the logger and the mesh will not be rendered 96 * (and will have an empty bounds) if any of the array lengths are invalid 97 * or if any of the values in the faces array are out of range. 98 * 99 * @since JavaFX 8.0 100 */ 101 public class TriangleMesh extends Mesh { 102 103 // The values in faces must be within range and the length of points, 104 // texCoords and faces must be divisible by 3, 2 and 6 respectively. 105 private final ObservableFloatArray points = FXCollections.observableFloatArray(); 106 private final ObservableFloatArray texCoords = FXCollections.observableFloatArray(); 107 private final ObservableFaceArray faces = new ObservableFaceArrayImpl(); 108 private final ObservableIntegerArray faceSmoothingGroups = FXCollections.observableIntegerArray(); 109 110 private final Listener pointsSyncer = new Listener(points); 111 private final Listener texCoordsSyncer = new Listener(texCoords); 112 private final Listener facesSyncer = new Listener(faces); 113 private final Listener faceSmoothingGroupsSyncer = new Listener(faceSmoothingGroups); 114 private final boolean isPredefinedShape; 115 private boolean isValidDirty = true; 116 private boolean isPointsValid, isTexCoordsValid, isFacesValid, isFaceSmoothingGroupValid; 117 private int refCount = 1; 118 119 private BaseBounds cachedBounds; 120 private VertexFormat vertexFormat = new VertexFormat(); 121 122 /** 123 * Creates a new instance of {@code TriangleMesh} class. 124 */ 125 public TriangleMesh() { 126 this(false); 127 } 128 129 TriangleMesh(boolean isPredefinedShape) { 130 this.isPredefinedShape = isPredefinedShape; 131 if (isPredefinedShape) { 132 isPointsValid = true; 133 isTexCoordsValid = true; 134 isFacesValid = true; 135 isFaceSmoothingGroupValid = true; 136 } else { 137 isPointsValid = false; 138 isTexCoordsValid = false; 139 isFacesValid = false; 140 isFaceSmoothingGroupValid = false; 141 } 142 } 143 144 /** 145 * Returns the number of elements that represents a Point. 146 * 147 * @return number of elements 148 */ 149 public final int getPointElementSize() { 150 return vertexFormat.getPointElementSize(); 151 } 152 153 /** 154 * Returns the number of elements that represents a TexCoord. 155 * 156 * @return number of elements 157 */ 158 public final int getTexCoordElementSize() { 159 return vertexFormat.getTexCoordElementSize(); 160 } 161 162 /** 163 * Returns the number of elements that represents a Face. 164 * 165 * @return number of elements 166 */ 167 public final int getFaceElementSize() { 168 return vertexFormat.getFaceElementSize(); 169 } 170 171 /** 172 * Gets the {@code points} array of this {@code TriangleMesh}. 173 * 174 * @return {@code points} array where each point is 175 * represented by 3 float values x, y and z, in that order. 176 */ 177 public final ObservableFloatArray getPoints() { 178 return points; 179 } 180 181 /** 182 * Gets the {@code texCoords} array of this {@code TriangleMesh}. 183 * The coordinates are proportional, so texture's top-left corner 184 * is at [0, 0] and bottom-right corner is at [1, 1]. 185 * 186 * @return {@code texCoord} array where each texture coordinate is represented 187 * by 2 float values: u and v, in that order. 188 */ 189 public final ObservableFloatArray getTexCoords() { 190 return texCoords; 191 } 192 193 /** 194 * Gets the {@code faces} array, indices into the {@code points} 195 * and {@code texCoords} arrays, of this {@code TriangleMesh} 196 * 197 * @return {@code faces} array where each face is 198 * 6 integers p0, t0, p1, t1, p3, t3, where p0, p1 and p2 are indices of 199 * points in {@code points} and t0, t1 and t2 are 200 * indices of texture coordinates in {@code texCoords}. 201 * Both indices are in terms of vertices (points or texture coordinates), 202 * not individual floats. 203 */ 204 public final ObservableFaceArray getFaces() { 205 return faces; 206 } 207 208 /** 209 * Gets the {@code faceSmoothingGroups} array of this {@code TriangleMesh}. 210 * Smoothing affects how a mesh is rendered but it does not effect its 211 * geometry. The face smoothing group value is used to control the smoothing 212 * between adjacent faces. 213 * 214 * <p> The face smoothing group value is represented by an array of bits and up to 215 * 32 unique groups is possible; (1 << 0) to (1 << 31). The face smoothing 216 * group value can range from 0 (no smoothing group) to all 32 groups. A face 217 * can belong to zero or more smoothing groups. A face is a member of group 218 * N if bit N is set, for example, groups |= (1 << N). A value of 0 implies 219 * no smoothing group or hard edges. 220 * Smoothing is applied when adjacent pair of faces shared a smoothing group. 221 * Otherwise the faces are rendered with a hard edge between them. 222 * 223 * <p> An empty faceSmoothingGroups implies all faces in this mesh have a 224 * smoothing group value of 1. 225 * 226 * <p> Note: If faceSmoothingGroups is not empty, is size must 227 * be equal to number of faces. 228 */ 229 public final ObservableIntegerArray getFaceSmoothingGroups() { 230 return faceSmoothingGroups; 231 } 232 233 @Override void setDirty(boolean value) { 234 super.setDirty(value); 235 if (!value) { // false 236 pointsSyncer.setDirty(false); 237 texCoordsSyncer.setDirty(false); 238 facesSyncer.setDirty(false); 239 faceSmoothingGroupsSyncer.setDirty(false); 240 } 241 } 242 243 int getRefCount() { 244 return refCount; 245 } 246 247 synchronized void incRef() { 248 this.refCount += 1; 249 } 250 251 synchronized void decRef() { 252 this.refCount -= 1; 253 } 254 255 private NGTriangleMesh peer; 256 257 /** 258 * @treatAsPrivate implementation detail 259 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 260 */ 261 @Deprecated 262 /** The peer node created by the graphics Toolkit/Pipeline implementation */ 263 NGTriangleMesh impl_getPGTriangleMesh() { 264 if (peer == null) { 265 peer = new NGTriangleMesh(); 266 } 267 return peer; 268 } 269 270 @Override 271 NGTriangleMesh getPGMesh() { 272 return impl_getPGTriangleMesh(); 273 } 274 275 private boolean validatePoints() { 276 if (points.size() == 0) { // Valid but meaningless for picking or rendering. 277 return false; 278 } 279 280 if ((points.size() % vertexFormat.getPointElementSize()) != 0) { 281 String logname = TriangleMesh.class.getName(); 282 PlatformLogger.getLogger(logname).warning("points.size() has " 283 + "to be divisible by getPointElementSize(). It is to" 284 + " store multiple x, y, and z coordinates of this mesh"); 285 return false; 286 } 287 return true; 288 } 289 290 private boolean validateTexCoords() { 291 if (texCoords.size() == 0) { // Valid but meaningless for picking or rendering. 292 return false; 293 } 294 295 if ((texCoords.size() % vertexFormat.getTexCoordElementSize()) != 0) { 296 String logname = TriangleMesh.class.getName(); 297 PlatformLogger.getLogger(logname).warning("texCoords.size() " 298 + "has to be divisible by getTexCoordElementSize()." 299 + " It is to store multiple u and v texture coordinates" 300 + " of this mesh"); 301 return false; 302 } 303 return true; 304 } 305 306 private boolean validateFaces() { 307 if (faces.size() == 0) { // Valid but meaningless for picking or rendering. 308 return false; 309 } 310 311 String logname = TriangleMesh.class.getName(); 312 if ((faces.size() % vertexFormat.getFaceElementSize()) != 0) { 313 PlatformLogger.getLogger(logname).warning("faces.size() has " 314 + "to be divisible by getFaceElementSize()."); 315 return false; 316 } 317 318 int nVerts = points.size() / vertexFormat.getPointElementSize(); 319 int nTVerts = texCoords.size() / vertexFormat.getTexCoordElementSize(); 320 for (int i = 0; i < faces.size(); i++) { 321 if (i % 2 == 0 && (faces.get(i) >= nVerts || faces.get(i) < 0) 322 || (i % 2 != 0 && (faces.get(i) >= nTVerts || faces.get(i) < 0))) { 323 PlatformLogger.getLogger(logname).warning("The values in the " 324 + "faces array must be within the range of the number " 325 + "of vertices in the points array (0 to points.length / 3 - 1) " 326 + "for the point indices and within the range of the " 327 + "number of the vertices in the texCoords array (0 to " 328 + "texCoords.length / 2 - 1) for the texture coordinate indices."); 329 return false; 330 } 331 } 332 return true; 333 } 334 335 private boolean validateFaceSmoothingGroups() { 336 if (faceSmoothingGroups.size() != 0 337 && faceSmoothingGroups.size() != (faces.size() / vertexFormat.getFaceElementSize())) { 338 String logname = TriangleMesh.class.getName(); 339 PlatformLogger.getLogger(logname).warning("faceSmoothingGroups.size()" 340 + " has to equal to number of faces."); 341 return false; 342 } 343 return true; 344 } 345 346 private boolean validate() { 347 if (isPredefinedShape) { 348 return true; 349 } 350 351 if (isValidDirty) { 352 if (pointsSyncer.dirtyInFull) { 353 isPointsValid = validatePoints(); 354 } 355 if (texCoordsSyncer.dirtyInFull) { 356 isTexCoordsValid = validateTexCoords(); 357 } 358 if (facesSyncer.dirty || pointsSyncer.dirtyInFull || texCoordsSyncer.dirtyInFull) { 359 isFacesValid = isPointsValid && isTexCoordsValid && validateFaces(); 360 } 361 if (faceSmoothingGroupsSyncer.dirtyInFull || facesSyncer.dirtyInFull) { 362 isFaceSmoothingGroupValid = isFacesValid && validateFaceSmoothingGroups(); 363 } 364 isValidDirty = false; 365 } 366 return isPointsValid && isTexCoordsValid && isFaceSmoothingGroupValid && isFacesValid; 367 } 368 369 /** 370 * @treatAsPrivate implementation detail 371 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 372 */ 373 @Deprecated 374 @Override 375 void impl_updatePG() { 376 if (!isDirty()) { 377 return; 378 } 379 380 final NGTriangleMesh pgTriMesh = impl_getPGTriangleMesh(); 381 if (validate()) { 382 pgTriMesh.syncPoints(pointsSyncer); 383 pgTriMesh.syncTexCoords(texCoordsSyncer); 384 pgTriMesh.syncFaces(facesSyncer); 385 pgTriMesh.syncFaceSmoothingGroups(faceSmoothingGroupsSyncer); 386 } else { 387 pgTriMesh.syncPoints(null); 388 pgTriMesh.syncTexCoords(null); 389 pgTriMesh.syncFaces(null); 390 pgTriMesh.syncFaceSmoothingGroups(null); 391 } 392 setDirty(false); 393 } 394 395 @Override 396 BaseBounds computeBounds(BaseBounds bounds) { 397 if (isDirty() || cachedBounds == null) { 398 cachedBounds = new BoxBounds(); 399 if (validate()) { 400 final double len = points.size(); 401 for (int i = 0; i < len; i += vertexFormat.getPointElementSize()) { 402 cachedBounds.add(points.get(i), points.get(i + 1), points.get(i + 2)); 403 } 404 } 405 } 406 return bounds.deriveWithNewBounds(cachedBounds); 407 } 408 409 /** 410 * Computes the centroid of the given triangle 411 * @param v0x x coord of first vertex of the triangle 412 * @param v0y y coord of first vertex of the triangle 413 * @param v0z z coord of first vertex of the triangle 414 * @param v1x x coord of second vertex of the triangle 415 * @param v1y y coord of second vertex of the triangle 416 * @param v1z z coord of second vertex of the triangle 417 * @param v2x x coord of third vertex of the triangle 418 * @param v2y y coord of third vertex of the triangle 419 * @param v2z z coord of third vertex of the triangle 420 * @return the triangle centroid 421 */ 422 private Point3D computeCentroid( 423 double v0x, double v0y, double v0z, 424 double v1x, double v1y, double v1z, 425 double v2x, double v2y, double v2z) { 426 427 // Point3D center = v1.midpoint(v2); 428 // Point3D vec = center.subtract(v0); 429 // return v0.add(new Point3D(vec.getX() / 3.0, vec.getY() / 3.0, vec.getZ() / 3.0)); 430 431 return new Point3D( 432 v0x + (v2x + (v1x - v2x) / 2.0 - v0x) / 3.0, 433 v0y + (v2y + (v1y - v2y) / 2.0 - v0y) / 3.0, 434 v0z + (v2z + (v1z - v2z) / 2.0 - v0z) / 3.0); 435 } 436 437 /** 438 * Computes the centroid of the given triangle 439 * @param v0 vertex of the triangle 440 * @param v1 vertex of the triangle 441 * @param v2 vertex of the triangle 442 * @return the triangle centroid 443 */ 444 private Point2D computeCentroid(Point2D v0, Point2D v1, Point2D v2) { 445 Point2D center = v1.midpoint(v2); 446 447 Point2D vec = center.subtract(v0); 448 return v0.add(new Point2D(vec.getX() / 3.0, vec.getY() / 3.0)); 449 } 450 451 /** 452 * Computes intersection of a pick ray and a single triangle face. 453 * 454 * It takes pickRay, origin and dir. The latter two can be of course obtained 455 * from the pickRay, but we need them to be converted to Point3D and don't 456 * want to do that for all faces. Therefore the conversion is done just once 457 * and passed to the method for all the faces. 458 * 459 * @param pickRay pick ray 460 * @param origin pick ray's origin 461 * @param dir pick ray's direction 462 * @param faceIndex index of the face to test 463 * @param cullFace cull face of the Node (and thus the tested face) 464 * @param candidate the owner node (for the possible placement to the result) 465 * @param reportFace whether or not to report he hit face 466 * @param result the pick result to be updated if a closer intersection is found 467 * @return true if the pick ray intersects with the face (regardless of whether 468 * the result has been updated) 469 */ 470 private boolean computeIntersectsFace( 471 PickRay pickRay, Vec3d origin, Vec3d dir, int faceIndex, 472 CullFace cullFace, Node candidate, boolean reportFace, PickResultChooser result) {//, BoxBounds rayBounds) { 473 474 // This computation was naturally done by Point3D and its operations, 475 // but it needs a lot of points and there is often a lot of triangles 476 // so it is vital for performance to use only primitive variables 477 // and do the computing manually. 478 479 int pointElementSize = vertexFormat.getPointElementSize(); 480 final int v0Idx = faces.get(faceIndex) * pointElementSize; 481 final int v1Idx = faces.get(faceIndex + 2) * pointElementSize; 482 final int v2Idx = faces.get(faceIndex + 4) * pointElementSize; 483 484 final float v0x = points.get(v0Idx); 485 final float v0y = points.get(v0Idx + 1); 486 final float v0z = points.get(v0Idx + 2); 487 final float v1x = points.get(v1Idx); 488 final float v1y = points.get(v1Idx + 1); 489 final float v1z = points.get(v1Idx + 2); 490 final float v2x = points.get(v2Idx); 491 final float v2y = points.get(v2Idx + 1); 492 final float v2z = points.get(v2Idx + 2); 493 494 // e1 = v1.subtract(v0) 495 final float e1x = v1x - v0x; 496 final float e1y = v1y - v0y; 497 final float e1z = v1z - v0z; 498 // e2 = v2.subtract(v0) 499 final float e2x = v2x - v0x; 500 final float e2y = v2y - v0y; 501 final float e2z = v2z - v0z; 502 503 // h = dir.crossProduct(e2) 504 final double hx = dir.y * e2z - dir.z * e2y; 505 final double hy = dir.z * e2x - dir.x * e2z; 506 final double hz = dir.x * e2y - dir.y * e2x; 507 508 // a = e1.dotProduct(h) 509 final double a = e1x * hx + e1y * hy + e1z * hz; 510 if (a == 0.0) { 511 return false; 512 } 513 final double f = 1.0 / a; 514 515 // s = origin.subtract(v0) 516 final double sx = origin.x - v0x; 517 final double sy = origin.y - v0y; 518 final double sz = origin.z - v0z; 519 520 // u = f * (s.dotProduct(h)) 521 final double u = f * (sx * hx + sy * hy + sz * hz); 522 523 if (u < 0.0 || u > 1.0) { 524 return false; 525 } 526 527 // q = s.crossProduct(e1) 528 final double qx = sy * e1z - sz * e1y; 529 final double qy = sz * e1x - sx * e1z; 530 final double qz = sx * e1y - sy * e1x; 531 532 // v = f * dir.dotProduct(q) 533 double v = f * (dir.x * qx + dir.y * qy + dir.z * qz); 534 535 if (v < 0.0 || u + v > 1.0) { 536 return false; 537 } 538 539 // t = f * e2.dotProduct(q) 540 final double t = f * (e2x * qx + e2y * qy + e2z * qz); 541 542 if (t >= pickRay.getNearClip() && t <= pickRay.getFarClip()) { 543 // This branch is entered only for hit triangles (not so often), 544 // so we can get smoothly back to the nice code using Point3Ds. 545 546 if (cullFace != CullFace.NONE) { 547 // normal = e1.crossProduct(e2) 548 final Point3D normal = new Point3D( 549 e1y * e2z - e1z * e2y, 550 e1z * e2x - e1x * e2z, 551 e1x * e2y - e1y * e2x); 552 553 final double nangle = normal.angle( 554 new Point3D(-dir.x, -dir.y, -dir.z)); 555 if ((nangle >= 90 || cullFace != CullFace.BACK) && 556 (nangle <= 90 || cullFace != CullFace.FRONT)) { 557 // hit culled face 558 return false; 559 } 560 } 561 562 if (Double.isInfinite(t) || Double.isNaN(t)) { 563 // we've got a nonsense pick ray or triangle 564 return false; 565 } 566 567 if (result == null || !result.isCloser(t)) { 568 // it intersects, but we are not interested in the result 569 // or we already have a better (closer) result 570 // so we can omit the point and texture computation 571 return true; 572 } 573 574 Point3D point = PickResultChooser.computePoint(pickRay, t); 575 576 // Now compute texture mapping. First rotate the triangle 577 // so that we can compute in 2D 578 579 // centroid = computeCentroid(v0, v1, v2); 580 final Point3D centroid = computeCentroid( 581 v0x, v0y, v0z, 582 v1x, v1y, v1z, 583 v2x, v2y, v2z); 584 585 // cv0 = v0.subtract(centroid) 586 final Point3D cv0 = new Point3D( 587 v0x - centroid.getX(), 588 v0y - centroid.getY(), 589 v0z - centroid.getZ()); 590 // cv1 = v1.subtract(centroid) 591 final Point3D cv1 = new Point3D( 592 v1x - centroid.getX(), 593 v1y - centroid.getY(), 594 v1z - centroid.getZ()); 595 // cv2 = v2.subtract(centroid) 596 final Point3D cv2 = new Point3D( 597 v2x - centroid.getX(), 598 v2y - centroid.getY(), 599 v2z - centroid.getZ()); 600 601 final Point3D ce1 = cv1.subtract(cv0); 602 final Point3D ce2 = cv2.subtract(cv0); 603 Point3D n = ce1.crossProduct(ce2); 604 if (n.getZ() < 0) { 605 n = new Point3D(-n.getX(), -n.getY(), -n.getZ()); 606 } 607 final Point3D ax = n.crossProduct(Rotate.Z_AXIS); 608 final double angle = Math.atan2(ax.magnitude(), n.dotProduct(Rotate.Z_AXIS)); 609 610 Rotate r = new Rotate(Math.toDegrees(angle), ax); 611 final Point3D crv0 = r.transform(cv0); 612 final Point3D crv1 = r.transform(cv1); 613 final Point3D crv2 = r.transform(cv2); 614 final Point3D rPoint = r.transform(point.subtract(centroid)); 615 616 final Point2D flatV0 = new Point2D(crv0.getX(), crv0.getY()); 617 final Point2D flatV1 = new Point2D(crv1.getX(), crv1.getY()); 618 final Point2D flatV2 = new Point2D(crv2.getX(), crv2.getY()); 619 final Point2D flatPoint = new Point2D(rPoint.getX(), rPoint.getY()); 620 621 // Obtain the texture triangle 622 int texCoordElementSize = vertexFormat.getTexCoordElementSize(); 623 final int t0Idx = faces.get(faceIndex + 1) * texCoordElementSize; 624 final int t1Idx = faces.get(faceIndex + 3) * texCoordElementSize; 625 final int t2Idx = faces.get(faceIndex + 5) * texCoordElementSize; 626 627 final Point2D u0 = new Point2D(texCoords.get(t0Idx), texCoords.get(t0Idx + 1)); 628 final Point2D u1 = new Point2D(texCoords.get(t1Idx), texCoords.get(t1Idx + 1)); 629 final Point2D u2 = new Point2D(texCoords.get(t2Idx), texCoords.get(t2Idx + 1)); 630 631 final Point2D txCentroid = computeCentroid(u0, u1, u2); 632 633 final Point2D cu0 = u0.subtract(txCentroid); 634 final Point2D cu1 = u1.subtract(txCentroid); 635 final Point2D cu2 = u2.subtract(txCentroid); 636 637 // Find the transform between the two triangles 638 639 final Affine src = new Affine( 640 flatV0.getX(), flatV1.getX(), flatV2.getX(), 641 flatV0.getY(), flatV1.getY(), flatV2.getY()); 642 final Affine trg = new Affine( 643 cu0.getX(), cu1.getX(), cu2.getX(), 644 cu0.getY(), cu1.getY(), cu2.getY()); 645 646 Point2D txCoords = null; 647 648 try { 649 src.invert(); 650 trg.append(src); 651 txCoords = txCentroid.add(trg.transform(flatPoint)); 652 } catch (NonInvertibleTransformException e) { 653 // Can't compute texture mapping, probably the coordinates 654 // don't make sense. Ignore it and return null tex coords. 655 } 656 657 result.offer(candidate, t, 658 reportFace ? faceIndex / vertexFormat.getFaceElementSize() : PickResult.FACE_UNDEFINED, 659 point, txCoords); 660 return true; 661 } 662 663 return false; 664 } 665 666 667 /** 668 * @treatAsPrivate implementation detail 669 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 670 */ 671 @Override 672 @Deprecated 673 protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult, 674 Node candidate, CullFace cullFace, boolean reportFace) { 675 676 boolean found = false; 677 if (validate()) { 678 final int size = faces.size(); 679 680 final Vec3d o = pickRay.getOriginNoClone(); 681 682 final Vec3d d = pickRay.getDirectionNoClone(); 683 684 for (int i = 0; i < size; i += vertexFormat.getFaceElementSize()) { 685 if (computeIntersectsFace(pickRay, o, d, i, cullFace, candidate, 686 reportFace, pickResult)) { 687 found = true; 688 } 689 } 690 } 691 return found; 692 } 693 694 private class Listener<T extends ObservableArray<T>> implements ArrayChangeListener<T>, FloatArraySyncer, IntegerArraySyncer { 695 696 protected final T array; 697 protected boolean dirty = true; 698 /** 699 * Array was replaced 700 * @return true if array was replaced; false otherwise 701 */ 702 protected boolean dirtyInFull = true; 703 protected int dirtyRangeFrom; 704 protected int dirtyRangeLength; 705 706 public Listener(T array) { 707 this.array = array; 708 array.addListener(this); 709 } 710 711 /** 712 * Adds a dirty range 713 * @param from index of the first modified element 714 * @param length length of the modified range 715 */ 716 protected final void addDirtyRange(int from, int length) { 717 if (length > 0 && !dirtyInFull) { 718 markDirty(); 719 if (dirtyRangeLength == 0) { 720 dirtyRangeFrom = from; 721 dirtyRangeLength = length; 722 } else { 723 int fromIndex = Math.min(dirtyRangeFrom, from); 724 int toIndex = Math.max(dirtyRangeFrom + dirtyRangeLength, from + length); 725 dirtyRangeFrom = fromIndex; 726 dirtyRangeLength = toIndex - fromIndex; 727 } 728 } 729 } 730 731 protected void markDirty() { 732 dirty = true; 733 TriangleMesh.this.setDirty(true); 734 } 735 736 @Override 737 public void onChanged(T observableArray, boolean sizeChanged, int from, int to) { 738 if (sizeChanged) { 739 setDirty(true); 740 } else { 741 addDirtyRange(from, to - from); 742 } 743 isValidDirty = true; 744 } 745 746 /** 747 * @param dirty if true, the whole collection is marked as dirty; 748 * if false, the whole collection is marked as not-dirty 749 */ 750 public final void setDirty(boolean dirty) { 751 this.dirtyInFull = dirty; 752 if (dirty) { 753 markDirty(); 754 dirtyRangeFrom = 0; 755 dirtyRangeLength = array.size(); 756 } else { 757 this.dirty = false; 758 dirtyRangeFrom = dirtyRangeLength = 0; 759 } 760 } 761 762 @Override 763 public float[] syncTo(float[] array) { 764 ObservableFloatArray floatArray = (ObservableFloatArray) this.array; 765 if (dirtyInFull || array == null || array.length != floatArray.size()) { 766 // Always allocate a new array when size changes 767 return floatArray.toArray(null); 768 } 769 floatArray.copyTo(dirtyRangeFrom, array, dirtyRangeFrom, dirtyRangeLength); 770 return array; 771 } 772 773 @Override 774 public int[] syncTo(int[] array) { 775 ObservableIntegerArray intArray = (ObservableIntegerArray) this.array; 776 if (dirtyInFull || array == null || array.length != intArray.size()) { 777 // Always allocate a new array when size changes 778 return intArray.toArray(null); 779 } 780 intArray.copyTo(dirtyRangeFrom, array, dirtyRangeFrom, dirtyRangeLength); 781 return array; 782 } 783 } 784 }