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 }