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.MeshHelper;
  36 import com.sun.javafx.scene.shape.SphereHelper;
  37 import com.sun.javafx.sg.prism.NGNode;
  38 import com.sun.javafx.sg.prism.NGSphere;
  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 Sphere} class defines a 3 dimensional sphere with the specified size.
  49  * A {@code Sphere} is a 3D geometry primitive created with a given radius.
  50  * It is centered at the origin.
  51  *
  52  * @since JavaFX 8.0
  53  */
  54 public class Sphere extends Shape3D {
  55     static {
  56          // This is used by classes in different packages to get access to
  57          // private and package private methods.
  58         SphereHelper.setSphereAccessor(new SphereHelper.SphereAccessor() {
  59             @Override
  60             public NGNode doCreatePeer(Node node) {
  61                 return ((Sphere) node).doCreatePeer();
  62             }
  63 
  64             @Override
  65             public void doUpdatePeer(Node node) {
  66                 ((Sphere) node).doUpdatePeer();
  67             }
  68         });
  69     }
  70 
  71     static final int DEFAULT_DIVISIONS = 64;
  72     static final double DEFAULT_RADIUS = 1;
  73     private int divisions = DEFAULT_DIVISIONS;
  74     private TriangleMesh mesh;
  75 
  76     /**
  77      * Creates a new instance of {@code Sphere} of radius of 1.0.
  78      * The resolution defaults to MID_RESOLUTION divisions along sphere's axes.
  79      */
  80     public Sphere() {
  81         this(DEFAULT_RADIUS, DEFAULT_DIVISIONS);
  82     }
  83 
  84     /**
  85      * Creates a new instance of {@code Sphere} of a given radius.
  86      * The resolution defaults to MID_RESOLUTION divisions along sphere's axes.
  87      *
  88      * @param radius Radius
  89      */
  90     public Sphere(double radius) {
  91         this(radius, DEFAULT_DIVISIONS);
  92     }
  93 
  94     /**
  95      * Creates a new instance of {@code Sphere} of a given radius and number
  96      * of divisions.
  97      * The resolution is defined in terms of number of subdivisions along the
  98      * sphere's axes. More divisions lead to more finely tesselated objects.
  99      *
 100      * Note that divisions should be at least 1. Any value less than that will be
 101      * clamped to 1.
 102      *
 103      * @param radius Radius
 104      * @param divisions Divisions
 105      */
 106     public Sphere(double radius, int divisions) {
 107         SphereHelper.initHelper(this);
 108         this.divisions = divisions < 1 ? 1: divisions;
 109         setRadius(radius);
 110     }
 111 
 112     /**
 113      * Defines the radius of the Sphere.
 114      *
 115      * @defaultValue 1.0
 116      */
 117     private DoubleProperty radius;
 118 
 119     public final void setRadius(double value) {
 120         radiusProperty().set(value);
 121     }
 122 
 123     public final double getRadius() {
 124         return radius == null ? 1 : radius.get();
 125     }
 126 
 127     public final DoubleProperty radiusProperty() {
 128         if (radius == null) {
 129             radius = new SimpleDoubleProperty(Sphere.this, "radius", DEFAULT_RADIUS) {
 130                 @Override
 131                 public void invalidated() {
 132                     NodeHelper.markDirty(Sphere.this, DirtyBits.MESH_GEOM);
 133                     manager.invalidateSphereMesh(key);
 134                     key = 0;
 135                     impl_geomChanged();
 136                 }
 137             };
 138         }
 139         return radius;
 140     }
 141 
 142     /**
 143      * Retrieves the divisions attribute use to generate this sphere.
 144      *
 145      * @return the divisions attribute.
 146      */
 147     public int getDivisions() {
 148         return divisions;
 149     }
 150 
 151     /*
 152      * Note: This method MUST only be called via its accessor method.
 153      */
 154     private NGNode doCreatePeer() {
 155         return new NGSphere();
 156     }
 157 
 158     /*
 159      * Note: This method MUST only be called via its accessor method.
 160      */
 161     private void doUpdatePeer() {
 162         if (NodeHelper.isDirty(this, DirtyBits.MESH_GEOM)) {
 163             final NGSphere pgSphere = NodeHelper.getPeer(this);
 164             final float r = (float) getRadius();
 165             if (r < 0) {
 166                 pgSphere.updateMesh(null);
 167             } else {
 168                 if (key == 0) {
 169                     key = generateKey(r, divisions);
 170                 }
 171                 mesh = manager.getSphereMesh(r, divisions, key);
 172                 mesh.updatePG();
 173                 pgSphere.updateMesh(mesh.getPGTriangleMesh());
 174             }
 175         }
 176     }
 177 
 178     /**
 179      * @treatAsPrivate implementation detail
 180      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 181      */
 182     @Deprecated
 183     @Override
 184     public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
 185         final float r = (float) getRadius();
 186 
 187         if (r < 0) {
 188             return bounds.makeEmpty();
 189         }
 190 
 191         bounds = bounds.deriveWithNewBounds(-r, -r, -r, r, r ,r);
 192         bounds = tx.transform(bounds, bounds);
 193         return bounds;
 194     }
 195 
 196     /**
 197      * @treatAsPrivate implementation detail
 198      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 199      */
 200     @Deprecated
 201     @Override
 202     protected boolean impl_computeContains(double localX, double localY) {
 203         double r = getRadius();
 204         double n2 = localX * localX + localY * localY;
 205         return n2 <= r * r;
 206     }
 207 
 208     /**
 209      * @treatAsPrivate implementation detail
 210      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 211      */
 212     @Deprecated
 213     @Override
 214     protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult) {
 215 
 216         final boolean exactPicking = divisions < DEFAULT_DIVISIONS && mesh != null;
 217 
 218         final double r = getRadius();
 219         final Vec3d dir = pickRay.getDirectionNoClone();
 220         final double dirX = dir.x;
 221         final double dirY = dir.y;
 222         final double dirZ = dir.z;
 223         final Vec3d origin = pickRay.getOriginNoClone();
 224         final double originX = origin.x;
 225         final double originY = origin.y;
 226         final double originZ = origin.z;
 227 
 228         // Coeficients of a quadratic equation desribing intersection with sphere
 229         final double a = dirX * dirX + dirY * dirY + dirZ * dirZ;
 230         final double b = 2 * (dirX * originX + dirY * originY + dirZ * originZ);
 231         final double c = originX * originX + originY * originY + originZ * originZ - r * r;
 232 
 233         final double discriminant = b * b - 4 * a * c;
 234         if (discriminant < 0) {
 235             // No real roots of the equation, missed the shape
 236             return false;
 237         }
 238 
 239         final double distSqrt = Math.sqrt(discriminant);
 240         final double q = (b < 0) ? (-b - distSqrt) / 2.0 : (-b + distSqrt) / 2.0;
 241 
 242         double t0 = q / a;
 243         double t1 = c / q;
 244 
 245         if (t0 > t1) {
 246             final double temp = t0;
 247             t0 = t1;
 248             t1 = temp;
 249         }
 250 
 251         final double minDistance = pickRay.getNearClip();
 252         final double maxDistance = pickRay.getFarClip();
 253 
 254         if (t1 < minDistance || t0 > maxDistance) {
 255             // the sphere is out of clipping planes
 256             return false;
 257         }
 258 
 259         double t = t0;
 260         final CullFace cullFace = getCullFace();
 261         if (t0 < minDistance || cullFace == CullFace.FRONT) {
 262             if (t1 <= maxDistance && getCullFace() != CullFace.BACK) {
 263                 // picking the back wall
 264                 t = t1;
 265             } else {
 266                 // we are inside the sphere with the back wall culled, but the
 267                 // exact picking still needs to be done because the front faced
 268                 // triangles may still be in front of us
 269                 if (!exactPicking) {
 270                     return false;
 271                 }
 272             }
 273         }
 274 
 275         if (Double.isInfinite(t) || Double.isNaN(t)) {
 276             // We've got a nonsense pick ray or sphere size.
 277             return false;
 278         }
 279 
 280         if (exactPicking) {
 281             return MeshHelper.computeIntersects(mesh, pickRay, pickResult, this, cullFace, false);
 282         }
 283 
 284         if (pickResult != null && pickResult.isCloser(t)) {
 285             final Point3D point = PickResultChooser.computePoint(pickRay, t);
 286 
 287             // computing texture coords
 288             final Point3D proj = new Point3D(point.getX(), 0, point.getZ());
 289             final Point3D cross = proj.crossProduct(Rotate.Z_AXIS);
 290             double angle = proj.angle(Rotate.Z_AXIS);
 291             if (cross.getY() > 0) {
 292                 angle = 360 - angle;
 293             }
 294             Point2D txtCoords = new Point2D(1 - angle / 360, 0.5 + point.getY() / (2 * r));
 295 
 296             pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txtCoords);
 297         }
 298         return true;
 299     }
 300 
 301     private static int correctDivisions(int div) {
 302         return ((div + 3) / 4) * 4;
 303     }
 304 
 305     static TriangleMesh createMesh(int div, float r) {
 306         div = correctDivisions(div);
 307 
 308         // NOTE: still create mesh for degenerated sphere
 309         final int div2 = div / 2;
 310 
 311         final int nPoints = div * (div2 - 1) + 2;
 312         final int nTPoints = (div + 1) * (div2 - 1) + div * 2;
 313         final int nFaces = div * (div2 - 2) * 2 + div * 2;
 314 
 315         final float rDiv = 1.f / div;
 316 
 317         float points[] = new float[nPoints * 3];
 318         float tPoints[] = new float[nTPoints * 2];
 319         int faces[] = new int[nFaces * 6];
 320 
 321         int pPos = 0, tPos = 0;
 322 
 323         for (int y = 0; y < div2 - 1; ++y) {
 324             float va = rDiv * (y + 1 - div2 / 2) * 2 * (float) Math.PI;
 325             float sin_va = (float) Math.sin(va);
 326             float cos_va = (float) Math.cos(va);
 327 
 328             float ty = 0.5f + sin_va * 0.5f;
 329             for (int i = 0; i < div; ++i) {
 330                 double a = rDiv * i * 2 * (float) Math.PI;
 331                 float hSin = (float) Math.sin(a);
 332                 float hCos = (float) Math.cos(a);
 333                 points[pPos + 0] = hSin * cos_va * r;
 334                 points[pPos + 2] = hCos * cos_va * r;
 335                 points[pPos + 1] = sin_va * r;
 336                 tPoints[tPos + 0] = 1 - rDiv * i;
 337                 tPoints[tPos + 1] = ty;
 338                 pPos += 3;
 339                 tPos += 2;
 340             }
 341             tPoints[tPos + 0] = 0;
 342             tPoints[tPos + 1] = ty;
 343             tPos += 2;
 344         }
 345 
 346         points[pPos + 0] = 0;
 347         points[pPos + 1] = -r;
 348         points[pPos + 2] = 0;
 349         points[pPos + 3] = 0;
 350         points[pPos + 4] = r;
 351         points[pPos + 5] = 0;
 352         pPos += 6;
 353 
 354         int pS = (div2 - 1) * div;
 355 
 356         float textureDelta = 1.f / 256;
 357         for (int i = 0; i < div; ++i) {
 358             tPoints[tPos + 0] = 1.0f - rDiv * (0.5f + i);
 359             tPoints[tPos + 1] = textureDelta;
 360             tPos += 2;
 361         }
 362 
 363         for (int i = 0; i < div; ++i) {
 364             tPoints[tPos + 0] = 1.0f - rDiv * (0.5f + i);
 365             tPoints[tPos + 1] = 1 - textureDelta;
 366             tPos += 2;
 367         }
 368 
 369         int fIndex = 0;
 370         for (int y = 0; y < div2 - 2; ++y) {
 371             for (int x = 0; x < div; ++x) {
 372                 int p0 = y * div + x;
 373                 int p1 = p0 + 1;
 374                 int p2 = p0 + div;
 375                 int p3 = p1 + div;
 376 
 377                 int t0 = p0 + y;
 378                 int t1 = t0 + 1;
 379                 int t2 = t0 + (div + 1);
 380                 int t3 = t1 + (div + 1);
 381 
 382                 // add p0, p1, p2
 383                 faces[fIndex + 0] = p0;
 384                 faces[fIndex + 1] = t0;
 385                 faces[fIndex + 2] = p1 % div == 0 ? p1 - div : p1;
 386                 faces[fIndex + 3] = t1;
 387                 faces[fIndex + 4] = p2;
 388                 faces[fIndex + 5] = t2;
 389                 fIndex += 6;
 390 
 391                 // add p3, p2, p1
 392                 faces[fIndex + 0] = p3 % div == 0 ? p3 - div : p3;
 393                 faces[fIndex + 1] = t3;
 394                 faces[fIndex + 2] = p2;
 395                 faces[fIndex + 3] = t2;
 396                 faces[fIndex + 4] = p1 % div == 0 ? p1 - div : p1;
 397                 faces[fIndex + 5] = t1;
 398                 fIndex += 6;
 399             }
 400         }
 401 
 402         int p0 = pS;
 403         int tB = (div2 - 1) * (div + 1);
 404         for (int x = 0; x < div; ++x) {
 405             int p2 = x, p1 = x + 1, t0 = tB + x;
 406             faces[fIndex + 0] = p0;
 407             faces[fIndex + 1] = t0;
 408             faces[fIndex + 2] = p1 == div ? 0 : p1;
 409             faces[fIndex + 3] = p1;
 410             faces[fIndex + 4] = p2;
 411             faces[fIndex + 5] = p2;
 412             fIndex += 6;
 413         }
 414 
 415         p0 = p0 + 1;
 416         tB = tB + div;
 417         int pB = (div2 - 2) * div;
 418 
 419         for (int x = 0; x < div; ++x) {
 420             int p1 = pB + x, p2 = pB + x + 1, t0 = tB + x;
 421             int t1 = (div2 - 2) * (div + 1) + x, t2 = t1 + 1;
 422             faces[fIndex + 0] = p0;
 423             faces[fIndex + 1] = t0;
 424             faces[fIndex + 2] = p1;
 425             faces[fIndex + 3] = t1;
 426             faces[fIndex + 4] = p2 % div == 0 ? p2 - div : p2;
 427             faces[fIndex + 5] = t2;
 428             fIndex += 6;
 429         }
 430 
 431         TriangleMesh m = new TriangleMesh(true);
 432         m.getPoints().setAll(points);
 433         m.getTexCoords().setAll(tPoints);
 434         m.getFaces().setAll(faces);
 435         return m;
 436     }
 437 
 438     private static int generateKey(float r, int div) {
 439         int hash = 5;
 440         hash = 23 * hash + Float.floatToIntBits(r);
 441         hash = 23 * hash + div;
 442         return hash;
 443     }
 444 }