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 }