Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.shape; 027 028import com.sun.javafx.collections.FloatArraySyncer; 029import com.sun.javafx.collections.IntegerArraySyncer; 030import com.sun.javafx.geom.BaseBounds; 031import com.sun.javafx.geom.BoxBounds; 032import com.sun.javafx.geom.PickRay; 033import com.sun.javafx.geom.Vec3d; 034import com.sun.javafx.scene.input.PickResultChooser; 035import com.sun.javafx.sg.PGTriangleMesh; 036import com.sun.javafx.tk.Toolkit; 037import javafx.collections.ArrayChangeListener; 038import javafx.collections.FXCollections; 039import javafx.collections.ObservableArray; 040import javafx.collections.ObservableFloatArray; 041import javafx.collections.ObservableIntegerArray; 042import javafx.geometry.Point2D; 043import javafx.geometry.Point3D; 044import javafx.scene.Node; 045import javafx.scene.input.PickResult; 046import javafx.scene.transform.Affine; 047import javafx.scene.transform.NonInvertibleTransformException; 048import javafx.scene.transform.Rotate; 049 050/** 051 * Defines a 3D geometric object contains separate arrays of points, 052 * texture coordinates, and faces that describe a triangulated 053 * geometric mesh. 054 *<p> 055 * Note that the term point, as used in the method names and method 056 * descriptions, actually refers to a set of x, y, and z point 057 * representing the position of a single vertex. The term points (plural) is 058 * used to indicate sets of x, y, and z points for multiple vertices. 059 * Similarly, the term texCoord is used to indicate a set of u and v texture 060 * coordinates for a single vertex, while the term texCoords (plural) is used 061 * to indicate sets of u and v texture coordinates for multiple vertices. 062 * Lastly, the term face is used to indicate 3 set of interleaving points 063 * and texture coordinates that together represent the geometric topology of a 064 * single triangle, while the term faces (plural) is used to indicate sets of 065 * triangles (each represent by a face). 066 * <p> 067 * For example, the faces that represent a single textured rectangle, using 2 triangles, 068 * has the following data order: [ 069 * <p> 070 * p0, t0, p1, t1, p3, t3, // First triangle of a textured rectangle 071 * <p> 072 * p1, t1, p2, t2, p3, t3 // Second triangle of a textured rectangle 073 * <p> 074 * ] 075 * <p> 076 * where p0, p1, p2 and p3 are indices into the points array, and t0, t1, t2 077 * and t3 are indices into the texCoords array. 078 * 079 * <p> The maximum number of vertices in the mesh can not exceed 65536. 080 * Therefore the maximum array lengths are 65536 * 3 (x, y, z per point) for points, 081 * and 65536 * 2 (u, v per texture coordinate) for texCoords. 082 * The length of {@code points}, {@code texCoords}, and {@code faces} must be 083 * divisible by 3, 2, and 6 respectively. 084 * The values in the faces array must be within the range of the number of vertices 085 * in the points array (0 to points.length / 3 - 1) for the point indices and 086 * within the range of the number of the vertices in 087 * the texCoords array (0 to texCoords.length / 2 - 1) for the texture coordinate indices. 088 * 089 * <p> A warning will be recorded to the logger and the mesh will not be rendered 090 * (and will have an empty bounds) if any of the array lengths are invalid 091 * or if any of the values in the faces array are out of range. 092 * 093 * @since JavaFX 8 094 */ 095public class TriangleMesh extends Mesh { 096 097 public static final int NUM_COMPONENTS_PER_POINT = 3; 098 public static final int NUM_COMPONENTS_PER_TEXCOORD = 2; 099 public static final int NUM_COMPONENTS_PER_FACE = 6; 100 101 // TODO: 3D - Need to validate the size and range of these arrays. 102 // A warning will be recorded to the logger and the mesh will have an empty 103 // bounds if the validation failed. (RT-30451) 104 // The maximum length for points (65536 * 3), texCoords (65536 * 2) 105 // or faces (65536 * 6) arrays. The values in faces must be within range. 106 // The length of points, texCoords and faces must be divisible by 3, 2 and 6 respectively. 107 private final ObservableFloatArray points = FXCollections.observableFloatArray(); 108 private final ObservableFloatArray texCoords = FXCollections.observableFloatArray(); 109 private final ObservableIntegerArray faces = FXCollections.observableIntegerArray(); 110 private final ObservableIntegerArray faceSmoothingGroups = FXCollections.observableIntegerArray(); 111 112 private final Listener pointsSyncer = new Listener(points); 113 private final Listener texCoordsSyncer = new Listener(texCoords); 114 private final Listener facesSyncer = new Listener(faces); 115 private final Listener faceSmoothingGroupsSyncer = new Listener(faceSmoothingGroups); 116 117 private int refCount = 1; 118 119 private BaseBounds cachedBounds; 120 121 /** 122 * Creates a new instance of {@code TriangleMesh} class. 123 */ 124 public TriangleMesh() { 125 } 126 127 /** 128 * Gets the {@code ObservableFloatArray} of points of this {@code TriangleMesh}. 129 * 130 * @return {@code ObservableFloatArray} of points where each point is 131 * represented by 3 float values x, y and z, in that order. 132 */ 133 public ObservableFloatArray getPoints() { 134 return points; 135 } 136 137 /** 138 * Gets the {@code ObservableFloatArray} of texture coordinates of this {@code TriangleMesh}. 139 * 140 * @return {@code ObservableFloatArray} array of texture coordinates 141 * where each texture coordinate is represented by 2 float values: u and v, 142 * in that order 143 */ 144 public ObservableFloatArray getTexCoords() { 145 return texCoords; 146 } 147 148 /** 149 * Gets the {@code ObservableIntegerArray} of faces, indices into the points 150 * and texCoords arrays, of this {@code TriangleMesh} 151 * 152 * @return {@code ObservableIntegerArray} of faces where each face is 153 * 6 integers p0, t0, p1, t1, p3, t3, where p0, p1 and p2 are indices of 154 * points in points {@code ObservableFloatArray} and t0, t1 and t2 are 155 * indices of texture coordinates in texCoords {@code ObservableFloatArray}. 156 * Both indices are in terms of vertices (points or texCoords), not individual 157 * floats. 158 */ 159 public ObservableIntegerArray getFaces() { 160 return faces; 161 } 162 163 /** 164 * Gets the {@code ObservableIntegerArray} of face smoothing groups 165 * of this {@code TriangleMesh}. 166 * Smoothing affects how a mesh is rendered but it does not effect its 167 * geometry. The face smoothing group value is used to control the smoothing 168 * between adjacent faces. 169 * 170 * <p> The face smoothing group is represented by an array of bits and up to 171 * 32 unique groups is possible; (1 << 0) to (1 << 31). The face smoothing 172 * group value can range from 0 (no smoothing group) to all 32 groups. A face 173 * can belong to zero or more smoothing groups. A face is a member of group 174 * N if bit N is set, for example, groups |= (1 << N). A value of 0 implies 175 * no smoothing group or hard edges. 176 * Smoothing is applied when adjacent pair of faces shared a smoothing group. 177 * Otherwise the faces are rendered with a hard edge between them. 178 * 179 * <p> An empty faceSmoothingGroups implies all faces in this mesh have a 180 * smoothing group value of 1. 181 * 182 * <p> Note: If faceSmoothingGroups is not empty, is size must 183 * be equal to number of faces. 184 */ 185 public ObservableIntegerArray getFaceSmoothingGroups() { 186 return faceSmoothingGroups; 187 } 188 189 @Override void setDirty(boolean value) { 190 super.setDirty(value); 191 if (!value) { // false 192 pointsSyncer.setDirty(false); 193 texCoordsSyncer.setDirty(false); 194 facesSyncer.setDirty(false); 195 faceSmoothingGroupsSyncer.setDirty(false); 196 } 197 } 198 199 int getRefCount() { 200 return refCount; 201 } 202 203 synchronized void incRef() { 204 this.refCount += 1; 205 } 206 207 synchronized void decRef() { 208 this.refCount -= 1; 209 } 210 211 private PGTriangleMesh peer; 212 213 /** 214 * @treatAsPrivate implementation detail 215 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 216 */ 217 @Deprecated 218 /** The peer node created by the graphics Toolkit/Pipeline implementation */ 219 PGTriangleMesh impl_getPGTriangleMesh() { 220 if (peer == null) { 221 peer = Toolkit.getToolkit().createPGTriangleMesh(); 222 } 223 return peer; 224 } 225 226 @Override 227 PGTriangleMesh getPGMesh() { 228 return impl_getPGTriangleMesh(); 229 } 230 231 /** 232 * @treatAsPrivate implementation detail 233 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 234 */ 235 @Deprecated 236 @Override 237 void impl_updatePG() { 238 if (!isDirty()) { 239 return; 240 } 241 242 PGTriangleMesh pgTriMesh = impl_getPGTriangleMesh(); 243 // sync points 244 if (pointsSyncer.dirty) { 245 pgTriMesh.syncPoints(pointsSyncer); 246 } 247 if (texCoordsSyncer.dirty) { 248 pgTriMesh.syncTexCoords(texCoordsSyncer); 249 } 250 if (facesSyncer.dirty) { 251 pgTriMesh.syncFaces(facesSyncer); 252 } 253 if (faceSmoothingGroupsSyncer.dirty) { 254 pgTriMesh.syncFaceSmoothingGroups(faceSmoothingGroupsSyncer); 255 } 256 setDirty(false); 257 } 258 259 @Override 260 BaseBounds computeBounds(BaseBounds bounds) { 261 if (isDirty() || cachedBounds == null) { 262 cachedBounds = new BoxBounds(); 263 264 final double len = points.size(); 265 for (int i = 0; i < len; i += NUM_COMPONENTS_PER_POINT) { 266 cachedBounds.add(points.get(i), points.get(i + 1), points.get(i + 2)); 267 } 268 } 269 return bounds.deriveWithNewBounds(cachedBounds); 270 } 271 272 /** 273 * Computes the centroid of the given triangle 274 * @param v0 vertex of the triangle 275 * @param v1 vertex of the triangle 276 * @param v2 vertex of the triangle 277 * @return the triangle centroid 278 */ 279 private Point3D computeCentroid(Point3D v0, Point3D v1, Point3D v2) { 280 Point3D center = v1.midpoint(v2); 281 282 Point3D vec = center.subtract(v0); 283 return v0.add(new Point3D(vec.getX() / 3.0, vec.getY() / 3.0, vec.getZ() / 3.0)); 284 } 285 286 /** 287 * Computes the centroid of the given triangle 288 * @param v0 vertex of the triangle 289 * @param v1 vertex of the triangle 290 * @param v2 vertex of the triangle 291 * @return the triangle centroid 292 */ 293 private Point2D computeCentroid(Point2D v0, Point2D v1, Point2D v2) { 294 Point2D center = v1.midpoint(v2); 295 296 Point2D vec = center.subtract(v0); 297 return v0.add(new Point2D(vec.getX() / 3.0, vec.getY() / 3.0)); 298 } 299 300 /** 301 * Computes intersection of a pick ray and a single triangle face. 302 * 303 * It takes pickRay, origin and dir. The latter two can be of course obtained 304 * from the pickRay, but we need them to be converted to Point3D and don't 305 * want to do that for all faces. Therefore the conversion is done just once 306 * and passed to the method for all the faces. 307 * 308 * @param pickRay pick ray 309 * @param origin pick ray's origin 310 * @param dir pick ray's direction 311 * @param faceIndex index of the face to test 312 * @param cullFace cull face of the Node (and thus the tested face) 313 * @param candidate the owner node (for the possible placement to the result) 314 * @param reportFace whether or not to report he hit face 315 * @param result the pick result to be updated if a closer intersection is found 316 * @return true if the pick ray intersects with the face (regardless of whether 317 * the result has been updated) 318 */ 319 private boolean computeIntersectsFace( 320 PickRay pickRay, Point3D origin, Point3D dir, int faceIndex, 321 CullFace cullFace, Node candidate, boolean reportFace, PickResultChooser result) { 322 323 final int v0Idx = faces.get(faceIndex) * NUM_COMPONENTS_PER_POINT; 324 final int v1Idx = faces.get(faceIndex + 2) * NUM_COMPONENTS_PER_POINT; 325 final int v2Idx = faces.get(faceIndex + 4) * NUM_COMPONENTS_PER_POINT; 326 327 final Point3D v0 = new Point3D(points.get(v0Idx), points.get(v0Idx + 1), points.get(v0Idx + 2)); 328 final Point3D v1 = new Point3D(points.get(v1Idx), points.get(v1Idx + 1), points.get(v1Idx + 2)); 329 final Point3D v2 = new Point3D(points.get(v2Idx), points.get(v2Idx + 1), points.get(v2Idx + 2)); 330 331 final Point3D e1 = v1.subtract(v0); 332 final Point3D e2 = v2.subtract(v0); 333 334 final Point3D h = dir.crossProduct(e2); 335 336 final double a = e1.dotProduct(h); 337 if (a == 0.0) { 338 return false; 339 } 340 final double f = 1.0 / a; 341 342 final Point3D s = origin.subtract(v0); 343 344 final double u = f * (s.dotProduct(h)); 345 346 if (u < 0.0 || u > 1.0) { 347 return false; 348 } 349 350 Point3D q = s.crossProduct(e1); 351 double v = f * dir.dotProduct(q); 352 353 if (v < 0.0 || u + v > 1.0) { 354 return false; 355 } 356 357 final double t = f * e2.dotProduct(q); 358 359 if (t >= pickRay.getNearClip() && t <= pickRay.getFarClip()) { 360 if (cullFace != CullFace.NONE) { 361 final Point3D normal = e1.crossProduct(e2); 362 final double nangle = normal.angle( 363 new Point3D(-dir.getX(), -dir.getY(), -dir.getZ())); 364 if ((nangle >= 90 || cullFace != CullFace.BACK) && 365 (nangle <= 90 || cullFace != CullFace.FRONT)) { 366 // hit culled face 367 return false; 368 } 369 } 370 371 if (Double.isInfinite(t) || Double.isNaN(t)) { 372 // we've got a nonsense pick ray or triangle 373 return false; 374 } 375 376 if (result == null || !result.isCloser(t)) { 377 // it intersects, but we are not interested in the result 378 // or we already have a better (closer) result 379 // so we can omit the point and texture computation 380 return true; 381 } 382 383 Point3D point = PickResultChooser.computePoint(pickRay, t); 384 385 // Now compute texture mapping. First rotate the triangle 386 // so that we can compute in 2D 387 388 final Point3D centroid = computeCentroid(v0, v1, v2); 389 final Point3D cv0 = v0.subtract(centroid); 390 final Point3D cv1 = v1.subtract(centroid); 391 final Point3D cv2 = v2.subtract(centroid); 392 393 final Point3D ce1 = cv1.subtract(cv0); 394 final Point3D ce2 = cv2.subtract(cv0); 395 Point3D n = ce1.crossProduct(ce2); 396 if (n.getZ() < 0) { 397 n = new Point3D(-n.getX(), -n.getY(), -n.getZ()); 398 } 399 final Point3D ax = n.crossProduct(Rotate.Z_AXIS); 400 final double angle = Math.atan2(ax.magnitude(), n.dotProduct(Rotate.Z_AXIS)); 401 402 Rotate r = new Rotate(Math.toDegrees(angle), ax); 403 final Point3D crv0 = r.transform(cv0); 404 final Point3D crv1 = r.transform(cv1); 405 final Point3D crv2 = r.transform(cv2); 406 final Point3D rPoint = r.transform(point.subtract(centroid)); 407 408 final Point2D flatV0 = new Point2D(crv0.getX(), crv0.getY()); 409 final Point2D flatV1 = new Point2D(crv1.getX(), crv1.getY()); 410 final Point2D flatV2 = new Point2D(crv2.getX(), crv2.getY()); 411 final Point2D flatPoint = new Point2D(rPoint.getX(), rPoint.getY()); 412 413 // Obtain the texture triangle 414 415 final int t0Idx = faces.get(faceIndex + 1) * NUM_COMPONENTS_PER_TEXCOORD; 416 final int t1Idx = faces.get(faceIndex + 3) * NUM_COMPONENTS_PER_TEXCOORD; 417 final int t2Idx = faces.get(faceIndex + 5) * NUM_COMPONENTS_PER_TEXCOORD; 418 419 final Point2D u0 = new Point2D(texCoords.get(t0Idx), texCoords.get(t0Idx + 1)); 420 final Point2D u1 = new Point2D(texCoords.get(t1Idx), texCoords.get(t1Idx + 1)); 421 final Point2D u2 = new Point2D(texCoords.get(t2Idx), texCoords.get(t2Idx + 1)); 422 423 final Point2D txCentroid = computeCentroid(u0, u1, u2); 424 425 final Point2D cu0 = u0.subtract(txCentroid); 426 final Point2D cu1 = u1.subtract(txCentroid); 427 final Point2D cu2 = u2.subtract(txCentroid); 428 429 // Find the transform between the two triangles 430 431 final Affine src = new Affine( 432 flatV0.getX(), flatV1.getX(), flatV2.getX(), 433 flatV0.getY(), flatV1.getY(), flatV2.getY()); 434 final Affine trg = new Affine( 435 cu0.getX(), cu1.getX(), cu2.getX(), 436 cu0.getY(), cu1.getY(), cu2.getY()); 437 438 Point2D txCoords = null; 439 440 try { 441 src.invert(); 442 trg.append(src); 443 txCoords = txCentroid.add(trg.transform(flatPoint)); 444 } catch (NonInvertibleTransformException e) { 445 // Can't compute texture mapping, probably the coordinates 446 // don't make sense. Ignore it and return null tex coords. 447 } 448 449 result.offer(candidate, t, 450 reportFace ? faceIndex / NUM_COMPONENTS_PER_FACE : PickResult.FACE_UNDEFINED, 451 point, txCoords); 452 return true; 453 } 454 455 return false; 456 } 457 458 459 /** 460 * @treatAsPrivate implementation detail 461 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 462 */ 463 @Override 464 @Deprecated 465 protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult, 466 Node candidate, CullFace cullFace, boolean reportFace) { 467 468 boolean found = false; 469 final int size = faces.size(); 470 471 final Vec3d o = pickRay.getOriginNoClone(); 472 final Point3D origin = new Point3D(o.x, o.y, o.z); 473 474 final Vec3d d = pickRay.getDirectionNoClone(); 475 final Point3D dir = new Point3D(d.x, d.y, d.z); 476 477 for (int i = 0; i < size; i += NUM_COMPONENTS_PER_FACE) { 478 if (computeIntersectsFace(pickRay, origin, dir, i, cullFace, candidate, 479 reportFace, pickResult)) { 480 found = true; 481 } 482 } 483 484 return found; 485 } 486 487 private class Listener<T extends ObservableArray<T>> implements ArrayChangeListener<T>, FloatArraySyncer, IntegerArraySyncer { 488 489 protected final T array; 490 protected boolean dirty; 491 /** 492 * Array was replaced 493 * @return true if array was replaced; false otherwise 494 */ 495 protected boolean dirtyInFull; 496 protected int dirtyRangeFrom; 497 protected int dirtyRangeLength; 498 499 public Listener(T array) { 500 this.array = array; 501 array.addListener(this); 502 } 503 504 /** 505 * Adds a dirty range 506 * @param from index of the first modified element 507 * @param length length of the modified range 508 */ 509 protected final void addDirtyRange(int from, int length) { 510 if (length > 0 && !dirtyInFull) { 511 markDirty(); 512 if (dirtyRangeLength == 0) { 513 dirtyRangeFrom = from; 514 dirtyRangeLength = length; 515 } else { 516 int fromIndex = Math.min(dirtyRangeFrom, from); 517 int toIndex = Math.max(dirtyRangeFrom + dirtyRangeLength, from + length); 518 dirtyRangeFrom = fromIndex; 519 dirtyRangeLength = toIndex - fromIndex; 520 } 521 } 522 } 523 524 protected void markDirty() { 525 dirty = true; 526 TriangleMesh.this.setDirty(true); 527 } 528 529 @Override 530 public void onChanged(T observableArray, boolean sizeChanged, int from, int to) { 531 if (sizeChanged) { 532 setDirty(true); 533 } else { 534 addDirtyRange(from, to - from); 535 } 536 } 537 538 /** 539 * @param dirty if true, the whole collection is marked as dirty; 540 * if false, the whole collection is marked as not-dirty 541 */ 542 public final void setDirty(boolean dirty) { 543 this.dirtyInFull = dirty; 544 if (dirty) { 545 markDirty(); 546 dirtyRangeFrom = 0; 547 dirtyRangeLength = array.size(); 548 } else { 549 this.dirty = false; 550 dirtyRangeFrom = dirtyRangeLength = 0; 551 } 552 } 553 554 @Override 555 public float[] syncTo(float[] array) { 556 ObservableFloatArray floatArray = (ObservableFloatArray) this.array; 557 if (dirtyInFull || array == null || array.length != floatArray.size()) { 558 return floatArray.toArray(array); 559 } 560 floatArray.copyTo(dirtyRangeFrom, array, dirtyRangeFrom, dirtyRangeLength); 561 return array; 562 } 563 564 @Override 565 public int[] syncTo(int[] array) { 566 ObservableIntegerArray intArray = (ObservableIntegerArray) this.array; 567 if (dirtyInFull || array == null || array.length != intArray.size()) { 568 return intArray.toArray(array); 569 } 570 intArray.copyTo(dirtyRangeFrom, array, dirtyRangeFrom, dirtyRangeLength); 571 return array; 572 } 573 } 574}