1 /* 2 * Copyright (c) 2013, 2016, 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.geom.BaseBounds; 29 import com.sun.javafx.geom.PickRay; 30 import com.sun.javafx.geom.Vec3d; 31 import com.sun.javafx.geom.transform.BaseTransform; 32 import com.sun.javafx.scene.DirtyBits; 33 import com.sun.javafx.scene.NodeHelper; 34 import com.sun.javafx.scene.input.PickResultChooser; 35 import com.sun.javafx.scene.shape.CylinderHelper; 36 import com.sun.javafx.scene.shape.MeshHelper; 37 import com.sun.javafx.sg.prism.NGCylinder; 38 import com.sun.javafx.sg.prism.NGNode; 39 import javafx.beans.property.DoubleProperty; 40 import javafx.beans.property.SimpleDoubleProperty; 41 import javafx.geometry.Point2D; 42 import javafx.geometry.Point3D; 43 import javafx.scene.Node; 44 import javafx.scene.input.PickResult; 45 import javafx.scene.transform.Rotate; 46 47 /** 48 * The {@code Cylinder} class defines a 3 dimensional cylinder with the specified size. 49 * A {@code Cylinder} is a 3D geometry primitive created with a given radius and height. 50 * It is centered at the origin. 51 * 52 * @since JavaFX 8.0 53 */ 54 public class Cylinder extends Shape3D { 55 static { 56 // This is used by classes in different packages to get access to 57 // private and package private methods. 58 CylinderHelper.setCylinderAccessor(new CylinderHelper.CylinderAccessor() { 59 @Override 60 public NGNode doCreatePeer(Node node) { 61 return ((Cylinder) node).doCreatePeer(); 62 } 63 64 @Override 65 public void doUpdatePeer(Node node) { 66 ((Cylinder) node).doUpdatePeer(); 67 } 68 69 }); 70 } 71 static final int DEFAULT_DIVISIONS = 64; 72 static final double DEFAULT_RADIUS = 1; 73 static final double DEFAULT_HEIGHT = 2; 74 75 private int divisions = DEFAULT_DIVISIONS; 76 private TriangleMesh mesh; 77 78 { 79 // To initialize the class helper at the begining each constructor of this class 80 CylinderHelper.initHelper(this); 81 } 82 /** 83 * Creates a new instance of {@code Cylinder} of radius of 1.0 and height of 2.0. 84 * Resolution defaults to 15 divisions along X and Z axis. 85 */ 86 public Cylinder() { 87 this(DEFAULT_RADIUS, DEFAULT_HEIGHT, DEFAULT_DIVISIONS); 88 } 89 90 /** 91 * Creates a new instance of {@code Cylinder} of a given radius and height. 92 * Resolution defaults to 15 divisions along X and Z axis. 93 * 94 * @param radius Radius 95 * @param height Height 96 */ 97 public Cylinder (double radius, double height) { 98 this(radius, height, DEFAULT_DIVISIONS); 99 } 100 101 /** 102 * Creates a new instance of {@code Cylinder} of a given radius, height, and 103 * divisions. Resolution defaults to 15 divisions along X and Z axis. 104 * 105 * Note that divisions should be at least 3. Any value less than that will be 106 * clamped to 3. 107 * 108 * @param radius Radius 109 * @param height Height 110 * @param divisions Divisions 111 */ 112 public Cylinder (double radius, double height, int divisions) { 113 this.divisions = divisions < 3 ? 3 : divisions; 114 setRadius(radius); 115 setHeight(height); 116 } 117 118 /** 119 * Defines the height or the Y dimension of the Cylinder. 120 * 121 * @defaultValue 2.0 122 */ 123 private DoubleProperty height; 124 125 public final void setHeight(double value) { 126 heightProperty().set(value); 127 } 128 129 public final double getHeight() { 130 return height == null ? 2 : height.get(); 131 } 132 133 public final DoubleProperty heightProperty() { 134 if (height == null) { 135 height = new SimpleDoubleProperty(Cylinder.this, "height", DEFAULT_HEIGHT) { 136 @Override 137 public void invalidated() { 138 NodeHelper.markDirty(Cylinder.this, DirtyBits.MESH_GEOM); 139 manager.invalidateCylinderMesh(key); 140 key = 0; 141 impl_geomChanged(); 142 } 143 }; 144 } 145 return height; 146 } 147 148 /** 149 * Defines the radius in the Z plane of the Cylinder. 150 * 151 * @defaultValue 1.0 152 */ 153 private DoubleProperty radius; 154 155 public final void setRadius(double value) { 156 radiusProperty().set(value); 157 } 158 159 public final double getRadius() { 160 return radius == null ? 1 : radius.get(); 161 } 162 163 public final DoubleProperty radiusProperty() { 164 if (radius == null) { 165 radius = new SimpleDoubleProperty(Cylinder.this, "radius", DEFAULT_RADIUS) { 166 @Override 167 public void invalidated() { 168 NodeHelper.markDirty(Cylinder.this, DirtyBits.MESH_GEOM); 169 manager.invalidateCylinderMesh(key); 170 key = 0; 171 impl_geomChanged(); 172 } 173 }; 174 } 175 return radius; 176 } 177 178 /** 179 * Retrieves the divisions attribute use to generate this cylinder. 180 * 181 * @return the divisions attribute. 182 */ 183 public int getDivisions() { 184 return divisions; 185 } 186 187 /* 188 * Note: This method MUST only be called via its accessor method. 189 */ 190 private void doUpdatePeer() { 191 if (NodeHelper.isDirty(this, DirtyBits.MESH_GEOM)) { 192 final NGCylinder peer = NodeHelper.getPeer(this); 193 final float h = (float) getHeight(); 194 final float r = (float) getRadius(); 195 if (h < 0 || r < 0) { 196 peer.updateMesh(null); 197 } else { 198 if (key == 0) { 199 key = generateKey(h, r, divisions); 200 } 201 mesh = manager.getCylinderMesh(h, r, divisions, key); 202 mesh.updatePG(); 203 peer.updateMesh(mesh.getPGTriangleMesh()); 204 } 205 } 206 } 207 208 /* 209 * Note: This method MUST only be called via its accessor method. 210 */ 211 private NGNode doCreatePeer() { 212 return new NGCylinder(); 213 } 214 215 /** 216 * @treatAsPrivate implementation detail 217 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 218 */ 219 @Deprecated 220 @Override 221 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 222 final float h = (float) getHeight(); 223 final float r = (float) getRadius(); 224 225 if (r < 0 || h < 0) { 226 return bounds.makeEmpty(); 227 } 228 229 final float hh = h * 0.5f; 230 231 bounds = bounds.deriveWithNewBounds(-r, -hh, -r, r, hh, r); 232 bounds = tx.transform(bounds, bounds); 233 return bounds; 234 } 235 236 /** 237 * @treatAsPrivate implementation detail 238 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 239 */ 240 @Deprecated 241 @Override 242 protected boolean impl_computeContains(double localX, double localY) { 243 double w = getRadius(); 244 double hh = getHeight()*.5f; 245 return -w <= localX && localX <= w && 246 -hh <= localY && localY <= hh; 247 } 248 249 /** 250 * @treatAsPrivate implementation detail 251 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 252 */ 253 @Deprecated 254 @Override 255 protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult) { 256 257 final boolean exactPicking = divisions < DEFAULT_DIVISIONS && mesh != null; 258 259 final double r = getRadius(); 260 final Vec3d dir = pickRay.getDirectionNoClone(); 261 final double dirX = dir.x; 262 final double dirY = dir.y; 263 final double dirZ = dir.z; 264 final Vec3d origin = pickRay.getOriginNoClone(); 265 final double originX = origin.x; 266 final double originY = origin.y; 267 final double originZ = origin.z; 268 final double h = getHeight(); 269 final double halfHeight = h / 2.0; 270 final CullFace cullFace = getCullFace(); 271 272 // Check the open cylinder first 273 274 // Coeficients of a quadratic equation desribing intersection with an infinite cylinder 275 final double a = dirX * dirX + dirZ * dirZ; 276 final double b = 2 * (dirX * originX + dirZ * originZ); 277 final double c = originX * originX + originZ * originZ - r * r; 278 279 final double discriminant = b * b - 4 * a * c; 280 281 double t0, t1, t = Double.POSITIVE_INFINITY; 282 final double minDistance = pickRay.getNearClip(); 283 final double maxDistance = pickRay.getFarClip(); 284 285 if (discriminant >= 0 && (dirX != 0.0 || dirZ != 0.0)) { 286 // the line hits the infinite cylinder 287 288 final double distSqrt = Math.sqrt(discriminant); 289 final double q = (b < 0) ? (-b - distSqrt) / 2.0 : (-b + distSqrt) / 2.0; 290 291 t0 = q / a; 292 t1 = c / q; 293 294 if (t0 > t1) { 295 double temp = t0; 296 t0 = t1; 297 t1 = temp; 298 } 299 300 // let's see if the hit is between clipping planes and within the cylinder's height 301 final double y0 = originY + t0 * dirY; 302 if (t0 < minDistance || y0 < -halfHeight || y0 > halfHeight || cullFace == CullFace.FRONT) { 303 final double y1 = originY + t1 * dirY; 304 if (t1 >= minDistance && t1 <= maxDistance && y1 >= -halfHeight && y1 <= halfHeight) { 305 if (cullFace != CullFace.BACK || exactPicking) { 306 // t0 is outside or behind but t1 hits. 307 308 // We need to do the exact picking even if the back wall 309 // is culled because the front facing triangles may 310 // still be in front of us 311 t = t1; 312 } 313 } // else no hit (but we need to check the caps) 314 } else if (t0 <= maxDistance) { 315 // t0 hits the height between clipping planes 316 t = t0; 317 } // else no hit (but we need to check the caps) 318 } 319 320 // Now check the caps 321 322 // if we already know we are going to do the exact picking, 323 // there is no need to check the caps 324 325 boolean topCap = false, bottomCap = false; 326 if (t == Double.POSITIVE_INFINITY || !exactPicking) { 327 final double tBottom = (-halfHeight - originY) / dirY; 328 final double tTop = (halfHeight - originY) / dirY; 329 boolean isT0Bottom = false; 330 331 if (tBottom < tTop) { 332 t0 = tBottom; 333 t1 = tTop; 334 isT0Bottom = true; 335 } else { 336 t0 = tTop; 337 t1 = tBottom; 338 } 339 340 if (t0 >= minDistance && t0 <= maxDistance && t0 < t && cullFace != CullFace.FRONT) { 341 final double tX = originX + dirX * t0; 342 final double tZ = originZ + dirZ * t0; 343 if (tX * tX + tZ * tZ <= r * r) { 344 bottomCap = isT0Bottom; topCap = !isT0Bottom; 345 t = t0; 346 } 347 } 348 349 if (t1 >= minDistance && t1 <= maxDistance && t1 < t && (cullFace != CullFace.BACK || exactPicking)) { 350 final double tX = originX + dirX * t1; 351 final double tZ = originZ + dirZ * t1; 352 if (tX * tX + tZ * tZ <= r * r) { 353 topCap = isT0Bottom; bottomCap = !isT0Bottom; 354 t = t1; 355 } 356 } 357 } 358 359 if (Double.isInfinite(t) || Double.isNaN(t)) { 360 // no hit 361 return false; 362 } 363 364 if (exactPicking) { 365 return MeshHelper.computeIntersects(mesh, pickRay, pickResult, this, cullFace, false); 366 } 367 368 if (pickResult != null && pickResult.isCloser(t)) { 369 final Point3D point = PickResultChooser.computePoint(pickRay, t); 370 371 Point2D txCoords; 372 if (topCap) { 373 txCoords = new Point2D( 374 0.5 + point.getX() / (2 * r), 375 0.5 + point.getZ() / (2 * r)); 376 } else if (bottomCap) { 377 txCoords = new Point2D( 378 0.5 + point.getX() / (2 * r), 379 0.5 - point.getZ() / (2 * r)); 380 } else { 381 final Point3D proj = new Point3D(point.getX(), 0, point.getZ()); 382 final Point3D cross = proj.crossProduct(Rotate.Z_AXIS); 383 double angle = proj.angle(Rotate.Z_AXIS); 384 if (cross.getY() > 0) { 385 angle = 360 - angle; 386 } 387 txCoords = new Point2D(1 - angle / 360, 0.5 + point.getY() / h); 388 } 389 390 pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txCoords); 391 } 392 return true; 393 } 394 395 static TriangleMesh createMesh(int div, float h, float r) { 396 397 // NOTE: still create mesh for degenerated cylinder 398 final int nPonits = div * 2 + 2; 399 final int tcCount = (div + 1) * 4 + 1; // 2 cap tex 400 final int faceCount = div * 4; 401 402 float textureDelta = 1.f / 256; 403 404 float dA = 1.f / div; 405 h *= .5f; 406 407 float points[] = new float[nPonits * 3]; 408 float tPoints[] = new float[tcCount * 2]; 409 int faces[] = new int[faceCount * 6]; 410 int smoothing[] = new int[faceCount]; 411 412 int pPos = 0, tPos = 0; 413 414 for (int i = 0; i < div; ++i) { 415 double a = dA * i * 2 * Math.PI; 416 417 points[pPos + 0] = (float) (Math.sin(a) * r); 418 points[pPos + 2] = (float) (Math.cos(a) * r); 419 points[pPos + 1] = h; 420 tPoints[tPos + 0] = 1 - dA * i; 421 tPoints[tPos + 1] = 1 - textureDelta; 422 pPos += 3; tPos += 2; 423 } 424 425 // top edge 426 tPoints[tPos + 0] = 0; 427 tPoints[tPos + 1] = 1 - textureDelta; 428 tPos += 2; 429 430 for (int i = 0; i < div; ++i) { 431 double a = dA * i * 2 * Math.PI; 432 points[pPos + 0] = (float) (Math.sin(a) * r); 433 points[pPos + 2] = (float) (Math.cos(a) * r); 434 points[pPos + 1] = -h; 435 tPoints[tPos + 0] = 1 - dA * i; 436 tPoints[tPos + 1] = textureDelta; 437 pPos += 3; tPos += 2; 438 } 439 440 // bottom edge 441 tPoints[tPos + 0] = 0; 442 tPoints[tPos + 1] = textureDelta; 443 tPos += 2; 444 445 // add cap central points 446 points[pPos + 0] = 0; 447 points[pPos + 1] = h; 448 points[pPos + 2] = 0; 449 points[pPos + 3] = 0; 450 points[pPos + 4] = -h; 451 points[pPos + 5] = 0; 452 pPos += 6; 453 454 // add cap central points 455 // bottom cap 456 for (int i = 0; i <= div; ++i) { 457 double a = (i < div) ? (dA * i * 2) * Math.PI: 0; 458 tPoints[tPos + 0] = (float) (Math.sin(a) * 0.5f) + 0.5f; 459 tPoints[tPos + 1] = (float) (Math.cos(a) * 0.5f) + 0.5f; 460 tPos += 2; 461 } 462 463 // top cap 464 for (int i = 0; i <= div; ++i) { 465 double a = (i < div) ? (dA * i * 2) * Math.PI: 0; 466 tPoints[tPos + 0] = 0.5f + (float) (Math.sin(a) * 0.5f); 467 tPoints[tPos + 1] = 0.5f - (float) (Math.cos(a) * 0.5f); 468 tPos += 2; 469 } 470 471 tPoints[tPos + 0] = .5f; 472 tPoints[tPos + 1] = .5f; 473 tPos += 2; 474 475 int fIndex = 0; 476 477 // build body faces 478 for (int p0 = 0; p0 < div; ++p0) { 479 int p1 = p0 + 1; 480 int p2 = p0 + div; 481 int p3 = p1 + div; 482 483 // add p0, p1, p2 484 faces[fIndex+0] = p0; 485 faces[fIndex+1] = p0; 486 faces[fIndex+2] = p2; 487 faces[fIndex+3] = p2 + 1; 488 faces[fIndex+4] = p1 == div ? 0 : p1; 489 faces[fIndex+5] = p1; 490 fIndex += 6; 491 492 // add p3, p2, p1 493 // *faces++ = SmFace(p3,p1,p2, p3,p1,p2, 1); 494 faces[fIndex+0] = p3 % div == 0 ? p3 - div : p3; 495 faces[fIndex+1] = p3 + 1; 496 faces[fIndex+2] = p1 == div ? 0 : p1; 497 faces[fIndex+3] = p1; 498 faces[fIndex+4] = p2; 499 faces[fIndex+5] = p2 + 1; 500 fIndex += 6; 501 502 } 503 // build cap faces 504 int tStart = (div + 1) * 2; 505 int t1 = (div + 1) * 4; 506 int p1 = div * 2; 507 508 // bottom cap 509 for (int p0 = 0; p0 < div; ++p0) { 510 int p2 = p0 + 1; 511 int t0 = tStart + p0; 512 int t2 = t0 + 1; 513 514 // add p0, p1, p2 515 faces[fIndex+0] = p0; 516 faces[fIndex+1] = t0; 517 faces[fIndex+2] = p2 == div ? 0 : p2; 518 faces[fIndex+3] = t2; 519 faces[fIndex+4] = p1; 520 faces[fIndex+5] = t1; 521 fIndex += 6; 522 } 523 524 p1 = div * 2 + 1; 525 tStart = (div + 1) * 3; 526 527 // top cap 528 for (int p0 = 0; p0 < div; ++p0) { 529 int p2 = p0 + 1 + div; 530 int t0 = tStart + p0; 531 int t2 = t0 + 1; 532 533 //*faces++ = SmFace(p0+div+1,p1,p2, t0,t1,t2, 2); 534 faces[fIndex+0] = p0 + div; 535 faces[fIndex+1] = t0; 536 faces[fIndex+2] = p1; 537 faces[fIndex+3] = t1; 538 faces[fIndex+4] = p2 % div == 0 ? p2 - div : p2; 539 faces[fIndex+5] = t2; 540 fIndex += 6; 541 } 542 543 for (int i = 0; i < div * 2; ++i) { 544 smoothing[i] = 1; 545 } 546 for (int i = div * 2; i < div * 4; ++i) { 547 smoothing[i] = 2; 548 } 549 550 TriangleMesh m = new TriangleMesh(true); 551 m.getPoints().setAll(points); 552 m.getTexCoords().setAll(tPoints); 553 m.getFaces().setAll(faces); 554 m.getFaceSmoothingGroups().setAll(smoothing); 555 556 return m; 557 } 558 559 private static int generateKey(float h, float r, int div) { 560 int hash = 7; 561 hash = 47 * hash + Float.floatToIntBits(h); 562 hash = 47 * hash + Float.floatToIntBits(r); 563 hash = 47 * hash + div; 564 return hash; 565 } 566 }