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.BoxHelper; 36 import com.sun.javafx.sg.prism.NGBox; 37 import com.sun.javafx.sg.prism.NGNode; 38 import javafx.beans.property.DoubleProperty; 39 import javafx.beans.property.SimpleDoubleProperty; 40 import javafx.geometry.Point2D; 41 import javafx.geometry.Point3D; 42 import javafx.scene.Node; 43 import javafx.scene.input.PickResult; 44 45 /** 46 * The {@code Box} class defines a 3 dimensional box with the specified size. 47 * A {@code Box} is a 3D geometry primitive created with a given depth, width, 48 * and height. It is centered at the origin. 49 * 50 * @since JavaFX 8.0 51 */ 52 public class Box extends Shape3D { 53 static { 54 // This is used by classes in different packages to get access to 55 // private and package private methods. 56 BoxHelper.setBoxAccessor(new BoxHelper.BoxAccessor() { 57 @Override 58 public NGNode doCreatePeer(Node node) { 59 return ((Box) node).doCreatePeer(); 60 } 61 62 @Override 63 public void doUpdatePeer(Node node) { 64 ((Box) node).doUpdatePeer(); 65 } 66 }); 67 } 68 69 private TriangleMesh mesh; 70 71 /** 72 * Creates a new instance of {@code Box} of dimension 2 by 2 by 2. 73 */ 74 75 public static final double DEFAULT_SIZE = 2; 76 77 { 78 // To initialize the class helper at the begining each constructor of this class 79 BoxHelper.initHelper(this); 80 } 81 82 public Box() { 83 this(DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE); 84 } 85 86 /** 87 * Creates a new instance of {@code Box} of dimension width by height 88 * by depth. 89 */ 90 public Box(double width, double height, double depth) { 91 setWidth(width); 92 setHeight(height); 93 setDepth(depth); 94 } 95 96 /** 97 * Defines the depth or the Z dimension of the Box. 98 * 99 * @defaultValue 2.0 100 */ 101 private DoubleProperty depth; 102 103 public final void setDepth(double value) { 104 depthProperty().set(value); 105 } 106 107 public final double getDepth() { 108 return depth == null ? 2 : depth.get(); 109 } 110 111 public final DoubleProperty depthProperty() { 112 if (depth == null) { 113 depth = new SimpleDoubleProperty(Box.this, "depth", DEFAULT_SIZE) { 114 @Override 115 public void invalidated() { 116 NodeHelper.markDirty(Box.this, DirtyBits.MESH_GEOM); 117 manager.invalidateBoxMesh(key); 118 key = 0; 119 impl_geomChanged(); 120 } 121 }; 122 } 123 return depth; 124 } 125 126 /** 127 * Defines the height or the Y dimension of the Box. 128 * 129 * @defaultValue 2.0 130 */ 131 private DoubleProperty height; 132 133 public final void setHeight(double value) { 134 heightProperty().set(value); 135 } 136 137 public final double getHeight() { 138 return height == null ? 2 : height.get(); 139 } 140 141 public final DoubleProperty heightProperty() { 142 if (height == null) { 143 height = new SimpleDoubleProperty(Box.this, "height", DEFAULT_SIZE) { 144 @Override 145 public void invalidated() { 146 NodeHelper.markDirty(Box.this, DirtyBits.MESH_GEOM); 147 manager.invalidateBoxMesh(key); 148 key = 0; 149 impl_geomChanged(); 150 } 151 }; 152 } 153 return height; 154 } 155 156 /** 157 * Defines the width or the X dimension of the Box. 158 * 159 * @defaultValue 2.0 160 */ 161 private DoubleProperty width; 162 163 public final void setWidth(double value) { 164 widthProperty().set(value); 165 } 166 167 public final double getWidth() { 168 return width == null ? 2 : width.get(); 169 } 170 171 public final DoubleProperty widthProperty() { 172 if (width == null) { 173 width = new SimpleDoubleProperty(Box.this, "width", DEFAULT_SIZE) { 174 @Override 175 public void invalidated() { 176 NodeHelper.markDirty(Box.this, DirtyBits.MESH_GEOM); 177 manager.invalidateBoxMesh(key); 178 key = 0; 179 impl_geomChanged(); 180 } 181 }; 182 } 183 return width; 184 } 185 186 /* 187 * Note: This method MUST only be called via its accessor method. 188 */ 189 private NGNode doCreatePeer() { 190 return new NGBox(); 191 } 192 193 /* 194 * Note: This method MUST only be called via its accessor method. 195 */ 196 private void doUpdatePeer() { 197 if (NodeHelper.isDirty(this, DirtyBits.MESH_GEOM)) { 198 NGBox peer = NodeHelper.getPeer(this); 199 final float w = (float) getWidth(); 200 final float h = (float) getHeight(); 201 final float d = (float) getDepth(); 202 if (w < 0 || h < 0 || d < 0) { 203 peer.updateMesh(null); 204 } else { 205 if (key == 0) { 206 key = generateKey(w, h, d); 207 } 208 mesh = manager.getBoxMesh(w, h, d, key); 209 mesh.updatePG(); 210 peer.updateMesh(mesh.getPGTriangleMesh()); 211 } 212 } 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 w = (float) getWidth(); 223 final float h = (float) getHeight(); 224 final float d = (float) getDepth(); 225 226 if (w < 0 || h < 0 || d < 0) { 227 return bounds.makeEmpty(); 228 } 229 230 final float hw = w * 0.5f; 231 final float hh = h * 0.5f; 232 final float hd = d * 0.5f; 233 234 bounds = bounds.deriveWithNewBounds(-hw, -hh, -hd, hw, hh, hd); 235 bounds = tx.transform(bounds, bounds); 236 return bounds; 237 } 238 239 /** 240 * @treatAsPrivate implementation detail 241 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 242 */ 243 @Deprecated 244 @Override 245 protected boolean impl_computeContains(double localX, double localY) { 246 double w = getWidth(); 247 double h = getHeight(); 248 return -w <= localX && localX <= w && 249 -h <= localY && localY <= h; 250 } 251 252 /** 253 * @treatAsPrivate implementation detail 254 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 255 */ 256 @Deprecated 257 @Override 258 protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult) { 259 260 final double w = getWidth(); 261 final double h = getHeight(); 262 final double d = getDepth(); 263 final double hWidth = w / 2.0; 264 final double hHeight = h / 2.0; 265 final double hDepth = d / 2.0; 266 final Vec3d dir = pickRay.getDirectionNoClone(); 267 final double invDirX = dir.x == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.x); 268 final double invDirY = dir.y == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.y); 269 final double invDirZ = dir.z == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.z); 270 final Vec3d origin = pickRay.getOriginNoClone(); 271 final double originX = origin.x; 272 final double originY = origin.y; 273 final double originZ = origin.z; 274 final boolean signX = invDirX < 0.0; 275 final boolean signY = invDirY < 0.0; 276 final boolean signZ = invDirZ < 0.0; 277 278 double t0 = Double.NEGATIVE_INFINITY; 279 double t1 = Double.POSITIVE_INFINITY; 280 char side0 = '0'; 281 char side1 = '0'; 282 283 if (Double.isInfinite(invDirX)) { 284 if (-hWidth <= originX && hWidth >= originX) { 285 // move on, we are inside for the whole length 286 } else { 287 return false; 288 } 289 } else { 290 t0 = ((signX ? hWidth : -hWidth) - originX) * invDirX; 291 t1 = ((signX ? -hWidth : hWidth) - originX) * invDirX; 292 side0 = signX ? 'X' : 'x'; 293 side1 = signX ? 'x' : 'X'; 294 } 295 296 if (Double.isInfinite(invDirY)) { 297 if (-hHeight <= originY && hHeight >= originY) { 298 // move on, we are inside for the whole length 299 } else { 300 return false; 301 } 302 } else { 303 final double ty0 = ((signY ? hHeight : -hHeight) - originY) * invDirY; 304 final double ty1 = ((signY ? -hHeight : hHeight) - originY) * invDirY; 305 306 if ((t0 > ty1) || (ty0 > t1)) { 307 return false; 308 } 309 if (ty0 > t0) { 310 side0 = signY ? 'Y' : 'y'; 311 t0 = ty0; 312 } 313 if (ty1 < t1) { 314 side1 = signY ? 'y' : 'Y'; 315 t1 = ty1; 316 } 317 } 318 319 if (Double.isInfinite(invDirZ)) { 320 if (-hDepth <= originZ && hDepth >= originZ) { 321 // move on, we are inside for the whole length 322 } else { 323 return false; 324 } 325 } else { 326 double tz0 = ((signZ ? hDepth : -hDepth) - originZ) * invDirZ; 327 double tz1 = ((signZ ? -hDepth : hDepth) - originZ) * invDirZ; 328 329 if ((t0 > tz1) || (tz0 > t1)) { 330 return false; 331 } 332 if (tz0 > t0) { 333 side0 = signZ ? 'Z' : 'z'; 334 t0 = tz0; 335 } 336 if (tz1 < t1) { 337 side1 = signZ ? 'z' : 'Z'; 338 t1 = tz1; 339 } 340 } 341 342 char side = side0; 343 double t = t0; 344 final CullFace cullFace = getCullFace(); 345 final double minDistance = pickRay.getNearClip(); 346 final double maxDistance = pickRay.getFarClip(); 347 348 if (t0 > maxDistance) { 349 return false; 350 } 351 if (t0 < minDistance || cullFace == CullFace.FRONT) { 352 if (t1 >= minDistance && t1 <= maxDistance && cullFace != CullFace.BACK) { 353 side = side1; 354 t = t1; 355 } else { 356 return false; 357 } 358 } 359 360 if (Double.isInfinite(t) || Double.isNaN(t)) { 361 // We've got a nonsense pick ray or box size. 362 return false; 363 } 364 365 if (pickResult != null && pickResult.isCloser(t)) { 366 Point3D point = PickResultChooser.computePoint(pickRay, t); 367 368 Point2D txtCoords = null; 369 370 switch (side) { 371 case 'x': // left 372 txtCoords = new Point2D( 373 0.5 - point.getZ() / d, 374 0.5 + point.getY() / h); 375 break; 376 case 'X': // right 377 txtCoords = new Point2D( 378 0.5 + point.getZ() / d, 379 0.5 + point.getY() / h); 380 break; 381 case 'y': // top 382 txtCoords = new Point2D( 383 0.5 + point.getX() / w, 384 0.5 - point.getZ() / d); 385 break; 386 case 'Y': // bottom 387 txtCoords = new Point2D( 388 0.5 + point.getX() / w, 389 0.5 + point.getZ() / d); 390 break; 391 case 'z': // front 392 txtCoords = new Point2D( 393 0.5 + point.getX() / w, 394 0.5 + point.getY() / h); 395 break; 396 case 'Z': // back 397 txtCoords = new Point2D( 398 0.5 - point.getX() / w, 399 0.5 + point.getY() / h); 400 break; 401 default: 402 // No hit with any of the planes. We must have had a zero 403 // pick ray direction vector. Should never happen. 404 return false; 405 } 406 407 pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txtCoords); 408 } 409 410 return true; 411 } 412 413 static TriangleMesh createMesh(float w, float h, float d) { 414 415 // NOTE: still create mesh for degenerated box 416 float hw = w / 2f; 417 float hh = h / 2f; 418 float hd = d / 2f; 419 420 float points[] = { 421 -hw, -hh, -hd, 422 hw, -hh, -hd, 423 hw, hh, -hd, 424 -hw, hh, -hd, 425 -hw, -hh, hd, 426 hw, -hh, hd, 427 hw, hh, hd, 428 -hw, hh, hd}; 429 430 float texCoords[] = {0, 0, 1, 0, 1, 1, 0, 1}; 431 432 // Specifies hard edges. 433 int faceSmoothingGroups[] = { 434 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 435 }; 436 437 int faces[] = { 438 0, 0, 2, 2, 1, 1, 439 2, 2, 0, 0, 3, 3, 440 1, 0, 6, 2, 5, 1, 441 6, 2, 1, 0, 2, 3, 442 5, 0, 7, 2, 4, 1, 443 7, 2, 5, 0, 6, 3, 444 4, 0, 3, 2, 0, 1, 445 3, 2, 4, 0, 7, 3, 446 3, 0, 6, 2, 2, 1, 447 6, 2, 3, 0, 7, 3, 448 4, 0, 1, 2, 5, 1, 449 1, 2, 4, 0, 0, 3, 450 }; 451 452 TriangleMesh mesh = new TriangleMesh(true); 453 mesh.getPoints().setAll(points); 454 mesh.getTexCoords().setAll(texCoords); 455 mesh.getFaces().setAll(faces); 456 mesh.getFaceSmoothingGroups().setAll(faceSmoothingGroups); 457 458 return mesh; 459 } 460 461 private static int generateKey(float w, float h, float d) { 462 int hash = 3; 463 hash = 97 * hash + Float.floatToIntBits(w); 464 hash = 97 * hash + Float.floatToIntBits(h); 465 hash = 97 * hash + Float.floatToIntBits(d); 466 return hash; 467 } 468 }