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.geom.BaseBounds; 029import com.sun.javafx.geom.PickRay; 030import com.sun.javafx.geom.Vec3d; 031import com.sun.javafx.geom.transform.BaseTransform; 032import com.sun.javafx.scene.DirtyBits; 033import com.sun.javafx.scene.input.PickResultChooser; 034import com.sun.javafx.sg.PGCylinder; 035import com.sun.javafx.sg.PGNode; 036import com.sun.javafx.tk.Toolkit; 037import javafx.beans.property.DoubleProperty; 038import javafx.beans.property.SimpleDoubleProperty; 039import javafx.geometry.Point2D; 040import javafx.geometry.Point3D; 041import javafx.scene.input.PickResult; 042import javafx.scene.transform.Rotate; 043 044/** 045 * The {@code Cylinder} class defines a 3 dimensional cylinder with the specified size. 046 * A {@code Cylinder} is a 3D geometry primitive created with a given radius and height. 047 * It is centered at the origin. 048 * 049 * @since JavaFX 8 050 */ 051public class Cylinder extends Shape3D { 052 053 static final int DEFAULT_DIVISIONS = 64; 054 static final double DEFAULT_RADIUS = 1; 055 static final double DEFAULT_HEIGHT = 2; 056 057 private int divisions = DEFAULT_DIVISIONS; 058 private TriangleMesh mesh; 059 060 /** 061 * Creates a new instance of {@code Cylinder} of radius of 1.0 and height of 2.0. 062 * Resolution defaults to 15 divisions along X and Z axis. 063 */ 064 public Cylinder() { 065 this(DEFAULT_RADIUS, DEFAULT_HEIGHT, DEFAULT_DIVISIONS); 066 } 067 068 /** 069 * Creates a new instance of {@code Cylinder} of a given radius and height. 070 * Resolution defaults to 15 divisions along X and Z axis. 071 * 072 * @param radius Radius 073 * @param height Height 074 */ 075 public Cylinder (double radius, double height) { 076 this(radius, height, DEFAULT_DIVISIONS); 077 } 078 079 /** 080 * Creates a new instance of {@code Cylinder} of a given radius, height, and 081 * divisions. Resolution defaults to 15 divisions along X and Z axis. 082 * 083 * Note that divisions should be at least 3. Any value less than that will be 084 * clamped to 3. 085 * 086 * @param radius Radius 087 * @param height Height 088 * @param divisions Divisions 089 */ 090 public Cylinder (double radius, double height, int divisions) { 091 this.divisions = divisions < 3 ? 3 : divisions; 092 setRadius(radius); 093 setHeight(height); 094 } 095 096 /** 097 * Defines the height or the Y dimension of the Cylinder. 098 * 099 * @defaultValue 2.0 100 */ 101 private DoubleProperty height; 102 103 public final void setHeight(double value) { 104 heightProperty().set(value); 105 } 106 107 public final double getHeight() { 108 return height == null ? 2 : height.get(); 109 } 110 111 public final DoubleProperty heightProperty() { 112 if (height == null) { 113 height = new SimpleDoubleProperty(Cylinder.this, "height", DEFAULT_HEIGHT) { 114 @Override 115 public void invalidated() { 116 impl_markDirty(DirtyBits.MESH_GEOM); 117 manager.invalidateCylinderMesh(key); 118 key = 0; 119 } 120 }; 121 } 122 return height; 123 } 124 125 /** 126 * Defines the radius in the Z plane of the Cylinder. 127 * 128 * @defaultValue 1.0 129 */ 130 private DoubleProperty radius; 131 132 public final void setRadius(double value) { 133 radiusProperty().set(value); 134 } 135 136 public final double getRadius() { 137 return radius == null ? 1 : radius.get(); 138 } 139 140 public final DoubleProperty radiusProperty() { 141 if (radius == null) { 142 radius = new SimpleDoubleProperty(Cylinder.this, "radius", DEFAULT_RADIUS) { 143 @Override 144 public void invalidated() { 145 impl_markDirty(DirtyBits.MESH_GEOM); 146 manager.invalidateCylinderMesh(key); 147 key = 0; 148 } 149 }; 150 } 151 return radius; 152 } 153 154 /** 155 * Retrieves the divisions attribute use to generate this cylinder. 156 * 157 * @return the divisions attribute. 158 */ 159 public int getDivisions() { 160 return divisions; 161 } 162 163 /** 164 * @treatAsPrivate implementation detail 165 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 166 */ 167 @Deprecated 168 @Override 169 public void impl_updatePG() { 170 super.impl_updatePG(); 171 if (impl_isDirty(DirtyBits.MESH_GEOM)) { 172 PGCylinder pgCylinder = (PGCylinder) impl_getPGNode(); 173 final float h = (float) getHeight(); 174 final float r = (float) getRadius(); 175 if (h < 0 || r < 0) { 176 pgCylinder.updateMesh(null); 177 } else { 178 if (key == 0) { 179 key = generateKey(h, r, divisions); 180 } 181 mesh = manager.getCylinderMesh(h, r, divisions, key); 182 mesh.impl_updatePG(); 183 pgCylinder.updateMesh(mesh.impl_getPGTriangleMesh()); 184 } 185 } 186 } 187 188 /** 189 * @treatAsPrivate implementation detail 190 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 191 */ 192 @Deprecated 193 @Override 194 protected PGNode impl_createPGNode() { 195 return Toolkit.getToolkit().createPGCylinder(); 196 } 197 198 /** 199 * @treatAsPrivate implementation detail 200 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 201 */ 202 @Deprecated 203 @Override 204 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 205 final float h = (float) getHeight(); 206 final float r = (float) getRadius(); 207 208 if (r < 0 || h < 0) { 209 return bounds.makeEmpty(); 210 } 211 212 final float hh = h * 0.5f; 213 214 bounds = bounds.deriveWithNewBounds(-r, -hh, -r, r, hh, r); 215 bounds = tx.transform(bounds, bounds); 216 return bounds; 217 } 218 219 /** 220 * @treatAsPrivate implementation detail 221 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 222 */ 223 @Deprecated 224 @Override 225 protected boolean impl_computeContains(double localX, double localY) { 226 double w = getRadius(); 227 double hh = getHeight()*.5f; 228 return -w <= localX && localX <= w && 229 -hh <= localY && localY <= hh; 230 } 231 232 /** 233 * @treatAsPrivate implementation detail 234 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 235 */ 236 @Deprecated 237 @Override 238 protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult) { 239 240 final boolean exactPicking = divisions < DEFAULT_DIVISIONS && mesh != null; 241 242 final double r = getRadius(); 243 final Vec3d dir = pickRay.getDirectionNoClone(); 244 final double dirX = dir.x; 245 final double dirY = dir.y; 246 final double dirZ = dir.z; 247 final Vec3d origin = pickRay.getOriginNoClone(); 248 final double originX = origin.x; 249 final double originY = origin.y; 250 final double originZ = origin.z; 251 final double h = getHeight(); 252 final double halfHeight = h / 2.0; 253 final CullFace cullFace = getCullFace(); 254 255 // Check the open cylinder first 256 257 // Coeficients of a quadratic equation desribing intersection with an infinite cylinder 258 final double a = dirX * dirX + dirZ * dirZ; 259 final double b = 2 * (dirX * originX + dirZ * originZ); 260 final double c = originX * originX + originZ * originZ - r * r; 261 262 final double discriminant = b * b - 4 * a * c; 263 264 double t0, t1, t = Double.POSITIVE_INFINITY; 265 final double minDistance = pickRay.getNearClip(); 266 final double maxDistance = pickRay.getFarClip(); 267 268 if (discriminant >= 0 && (dirX != 0.0 || dirZ != 0.0)) { 269 // the line hits the infinite cylinder 270 271 final double distSqrt = Math.sqrt(discriminant); 272 final double q = (b < 0) ? (-b - distSqrt) / 2.0 : (-b + distSqrt) / 2.0; 273 274 t0 = q / a; 275 t1 = c / q; 276 277 if (t0 > t1) { 278 double temp = t0; 279 t0 = t1; 280 t1 = temp; 281 } 282 283 // let's see if the hit is between clipping planes and within the cylinder's height 284 final double y0 = originY + t0 * dirY; 285 if (t0 < minDistance || y0 < -halfHeight || y0 > halfHeight || cullFace == CullFace.FRONT) { 286 final double y1 = originY + t1 * dirY; 287 if (t1 >= minDistance && t1 <= maxDistance && y1 >= -halfHeight && y1 <= halfHeight) { 288 if (cullFace != CullFace.BACK || exactPicking) { 289 // t0 is outside or behind but t1 hits. 290 291 // We need to do the exact picking even if the back wall 292 // is culled because the front facing triangles may 293 // still be in front of us 294 t = t1; 295 } 296 } // else no hit (but we need to check the caps) 297 } else if (t0 <= maxDistance) { 298 // t0 hits the height between clipping planes 299 t = t0; 300 } // else no hit (but we need to check the caps) 301 } 302 303 // Now check the caps 304 305 // if we already know we are going to do the exact picking, 306 // there is no need to check the caps 307 308 boolean topCap = false, bottomCap = false; 309 if (t == Double.POSITIVE_INFINITY || !exactPicking) { 310 final double tBottom = (-halfHeight - originY) / dirY; 311 final double tTop = (halfHeight - originY) / dirY; 312 boolean isT0Bottom = false; 313 314 if (tBottom < tTop) { 315 t0 = tBottom; 316 t1 = tTop; 317 isT0Bottom = true; 318 } else { 319 t0 = tTop; 320 t1 = tBottom; 321 } 322 323 if (t0 >= minDistance && t0 <= maxDistance && t0 < t && cullFace != CullFace.FRONT) { 324 final double tX = originX + dirX * t0; 325 final double tZ = originZ + dirZ * t0; 326 if (tX * tX + tZ * tZ <= r * r) { 327 bottomCap = isT0Bottom; topCap = !isT0Bottom; 328 t = t0; 329 } 330 } 331 332 if (t1 >= minDistance && t1 <= maxDistance && t1 < t && (cullFace != CullFace.BACK || exactPicking)) { 333 final double tX = originX + dirX * t1; 334 final double tZ = originZ + dirZ * t1; 335 if (tX * tX + tZ * tZ <= r * r) { 336 topCap = isT0Bottom; bottomCap = !isT0Bottom; 337 t = t1; 338 } 339 } 340 } 341 342 if (Double.isInfinite(t) || Double.isNaN(t)) { 343 // no hit 344 return false; 345 } 346 347 if (exactPicking) { 348 return mesh.impl_computeIntersects(pickRay, pickResult, this, cullFace, false); 349 } 350 351 if (pickResult != null && pickResult.isCloser(t)) { 352 final Point3D point = PickResultChooser.computePoint(pickRay, t); 353 354 Point2D txCoords; 355 if (topCap) { 356 txCoords = new Point2D( 357 0.5 + point.getX() / (2 * r), 358 0.5 + point.getZ() / (2 * r)); 359 } else if (bottomCap) { 360 txCoords = new Point2D( 361 0.5 + point.getX() / (2 * r), 362 0.5 - point.getZ() / (2 * r)); 363 } else { 364 final Point3D proj = new Point3D(point.getX(), 0, point.getZ()); 365 final Point3D cross = proj.crossProduct(Rotate.Z_AXIS); 366 double angle = proj.angle(Rotate.Z_AXIS); 367 if (cross.getY() > 0) { 368 angle = 360 - angle; 369 } 370 txCoords = new Point2D(1 - angle / 360, 0.5 + point.getY() / h); 371 } 372 373 pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txCoords); 374 } 375 return true; 376 } 377 378 static TriangleMesh createMesh(int div, float h, float r) { 379 380 // NOTE: still create mesh for degenerated cylinder 381 final int nPonits = (div + 1) * 2 + 2; 382 final int tcCount = (div + 1) * 4 + 1; // 2 cap tex 383 final int faceCount = div * 4; 384 385 float textureDelta = 1.f / 256; 386 387 float dA = 1.f / div; 388 h *= .5f; 389 390 float points[] = new float[nPonits * 3]; 391 float tPoints[] = new float[tcCount * 2]; 392 int faces[] = new int[faceCount * 6]; 393 int smoothing[] = new int[faceCount]; 394 395 int pPos = 0, tPos = 0; 396 397 for (int i = 0; i <= div; ++i) { 398 double a = (i < div) ? dA * i * 2 * Math.PI : 0; 399 400 points[pPos + 0] = (float) (Math.sin(a) * r); 401 points[pPos + 2] = (float) (Math.cos(a) * r); 402 points[pPos + 1] = h; 403 tPoints[tPos + 0] = 1 - dA * i; 404 tPoints[tPos + 1] = 1 - textureDelta; 405 pPos += 3; tPos += 2; 406 } 407 408 for (int i = 0; i <= div; ++i) { 409 double a = (i < div) ? dA * i * 2 * Math.PI : 0; 410 points[pPos + 0] = (float) (Math.sin(a) * r); 411 points[pPos + 2] = (float) (Math.cos(a) * r); 412 points[pPos + 1] = -h; 413 tPoints[tPos + 0] = 1 - dA * i; 414 tPoints[tPos + 1] = textureDelta; 415 pPos += 3; tPos += 2; 416 } 417 418 // add cap central points 419 points[pPos + 0] = 0; 420 points[pPos + 1] = h; 421 points[pPos + 2] = 0; 422 points[pPos + 3] = 0; 423 points[pPos + 4] = -h; 424 points[pPos + 5] = 0; 425 pPos += 6; 426 427 // add cap central points 428 // bottom cap 429 for (int i = 0; i <= div; ++i) { 430 double a = (i < div) ? (dA * i * 2) * Math.PI: 0; 431 tPoints[tPos + 0] = (float) (Math.sin(a) * 0.5f) + 0.5f; 432 tPoints[tPos + 1] = (float) (Math.cos(a) * 0.5f) + 0.5f; 433 tPos += 2; 434 } 435 436 // top cap 437 for (int i = 0; i <= div; ++i) { 438 double a = (i < div) ? (dA * i * 2) * Math.PI: 0; 439 tPoints[tPos + 0] = 0.5f + (float) (Math.sin(a) * 0.5f); 440 tPoints[tPos + 1] = 0.5f - (float) (Math.cos(a) * 0.5f); 441 tPos += 2; 442 } 443 444 tPoints[tPos + 0] = .5f; 445 tPoints[tPos + 1] = .5f; 446 tPos += 2; 447 448 int fIndex = 0; 449 450 // build body faces 451 for (int p0 = 0; p0 != div; ++p0) { 452 int p1 = p0 + 1; 453 int p2 = p0 + div + 1; 454 int p3 = p1 + div + 1; 455 456 // add p0, p1, p2 457 faces[fIndex+0] = p0; 458 faces[fIndex+1] = p0; 459 faces[fIndex+2] = p2; 460 faces[fIndex+3] = p2; 461 faces[fIndex+4] = p1; 462 faces[fIndex+5] = p1; 463 fIndex += 6; 464 465 // add p3, p2, p1 466 // *faces++ = SmFace(p3,p1,p2, p3,p1,p2, 1); 467 faces[fIndex+0] = p3; 468 faces[fIndex+1] = p3; 469 faces[fIndex+2] = p1; 470 faces[fIndex+3] = p1; 471 faces[fIndex+4] = p2; 472 faces[fIndex+5] = p2; 473 fIndex += 6; 474 475 } 476 // build cap faces 477 int tStart = (div + 1) * 2, t1 = (div + 1) * 4, p1 = (div + 1) * 2; 478 479 // bottom cap 480 for (int p0 = 0; p0 != div; ++p0) { 481 int p2 = p0 + 1, t0 = tStart + p0, t2 = t0 + 1; 482 // add p0, p1, p2 483 484 faces[fIndex+0] = p0; 485 faces[fIndex+1] = t0; 486 faces[fIndex+2] = p2; 487 faces[fIndex+3] = t2; 488 faces[fIndex+4] = p1; 489 faces[fIndex+5] = t1; 490 fIndex += 6; 491 } 492 493 p1 = (div + 1) * 2 + 1; 494 tStart = (div + 1) * 3; 495 496 // top cap 497 for (int p0 = 0; p0 != div; ++p0) { 498 int p2 = p0 + 1 + div + 1, t0 = tStart + p0, t2 = t0 + 1; 499 //*faces++ = SmFace(p0+div+1,p1,p2, t0,t1,t2, 2); 500 501 faces[fIndex+0] = p0 + div + 1; 502 faces[fIndex+1] = t0; 503 faces[fIndex+2] = p1; 504 faces[fIndex+3] = t1; 505 faces[fIndex+4] = p2; 506 faces[fIndex+5] = t2; 507 fIndex += 6; 508 } 509 510 for (int i = 0; i < div * 2; ++i) { 511 smoothing[i] = 1; 512 } 513 for (int i = div * 2; i < div * 4; ++i) { 514 smoothing[i] = 2; 515 } 516 517 TriangleMesh m = new TriangleMesh(); 518 m.getPoints().setAll(points); 519 m.getTexCoords().setAll(tPoints); 520 m.getFaces().setAll(faces); 521 m.getFaceSmoothingGroups().setAll(smoothing); 522 523 return m; 524 } 525 526 private static int generateKey(float h, float r, int div) { 527 int hash = 7; 528 hash = 47 * hash + Float.floatToIntBits(h); 529 hash = 47 * hash + Float.floatToIntBits(r); 530 hash = 47 * hash + div; 531 return hash; 532 } 533}