javafx.scene.shape.Box Maven / Gradle / Ivy
/*
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.shape;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.geom.Vec3d;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.scene.DirtyBits;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.input.PickResultChooser;
import com.sun.javafx.scene.shape.BoxHelper;
import com.sun.javafx.sg.prism.NGBox;
import com.sun.javafx.sg.prism.NGNode;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.Node;
import javafx.scene.input.PickResult;
/**
* The {@code Box} class defines a 3 dimensional box with the specified size.
* A {@code Box} is a 3D geometry primitive created with a given depth, width,
* and height. It is centered at the origin.
*
* @since JavaFX 8.0
*/
public class Box extends Shape3D {
static {
// This is used by classes in different packages to get access to
// private and package private methods.
BoxHelper.setBoxAccessor(new BoxHelper.BoxAccessor() {
@Override
public NGNode doCreatePeer(Node node) {
return ((Box) node).doCreatePeer();
}
@Override
public void doUpdatePeer(Node node) {
((Box) node).doUpdatePeer();
}
@Override
public BaseBounds doComputeGeomBounds(Node node,
BaseBounds bounds, BaseTransform tx) {
return ((Box) node).doComputeGeomBounds(bounds, tx);
}
@Override
public boolean doComputeContains(Node node, double localX, double localY) {
return ((Box) node).doComputeContains(localX, localY);
}
@Override
public boolean doComputeIntersects(Node node, PickRay pickRay,
PickResultChooser pickResult) {
return ((Box) node).doComputeIntersects(pickRay, pickResult);
}
});
}
private TriangleMesh mesh;
public static final double DEFAULT_SIZE = 2;
{
// To initialize the class helper at the beginning of each constructor of this class
BoxHelper.initHelper(this);
}
/**
* Creates a new instance of {@code Box} of dimension 2 by 2 by 2.
*/
public Box() {
this(DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE);
}
/**
* Creates a new instance of {@code Box} of dimension width by height
* by depth.
* @param width the width of this box
* @param height the height of this box
* @param depth the depth of this box
*/
public Box(double width, double height, double depth) {
setWidth(width);
setHeight(height);
setDepth(depth);
}
/**
* Defines the depth or the Z dimension of the Box.
*
* @defaultValue 2.0
*/
private DoubleProperty depth;
public final void setDepth(double value) {
depthProperty().set(value);
}
public final double getDepth() {
return depth == null ? 2 : depth.get();
}
public final DoubleProperty depthProperty() {
if (depth == null) {
depth = new SimpleDoubleProperty(Box.this, "depth", DEFAULT_SIZE) {
@Override
public void invalidated() {
NodeHelper.markDirty(Box.this, DirtyBits.MESH_GEOM);
manager.invalidateBoxMesh(key);
key = null;
NodeHelper.geomChanged(Box.this);
}
};
}
return depth;
}
/**
* Defines the height or the Y dimension of the Box.
*
* @defaultValue 2.0
*/
private DoubleProperty height;
public final void setHeight(double value) {
heightProperty().set(value);
}
public final double getHeight() {
return height == null ? 2 : height.get();
}
public final DoubleProperty heightProperty() {
if (height == null) {
height = new SimpleDoubleProperty(Box.this, "height", DEFAULT_SIZE) {
@Override
public void invalidated() {
NodeHelper.markDirty(Box.this, DirtyBits.MESH_GEOM);
manager.invalidateBoxMesh(key);
key = null;
NodeHelper.geomChanged(Box.this);
}
};
}
return height;
}
/**
* Defines the width or the X dimension of the Box.
*
* @defaultValue 2.0
*/
private DoubleProperty width;
public final void setWidth(double value) {
widthProperty().set(value);
}
public final double getWidth() {
return width == null ? 2 : width.get();
}
public final DoubleProperty widthProperty() {
if (width == null) {
width = new SimpleDoubleProperty(Box.this, "width", DEFAULT_SIZE) {
@Override
public void invalidated() {
NodeHelper.markDirty(Box.this, DirtyBits.MESH_GEOM);
manager.invalidateBoxMesh(key);
key = null;
NodeHelper.geomChanged(Box.this);
}
};
}
return width;
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private NGNode doCreatePeer() {
return new NGBox();
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private void doUpdatePeer() {
if (NodeHelper.isDirty(this, DirtyBits.MESH_GEOM)) {
NGBox peer = NodeHelper.getPeer(this);
final float w = (float) getWidth();
final float h = (float) getHeight();
final float d = (float) getDepth();
if (w < 0 || h < 0 || d < 0) {
peer.updateMesh(null);
} else {
if (key == null) {
key = new BoxKey(w, h, d);
}
mesh = manager.getBoxMesh(w, h, d, key);
mesh.updatePG();
peer.updateMesh(mesh.getPGTriangleMesh());
}
}
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
final float w = (float) getWidth();
final float h = (float) getHeight();
final float d = (float) getDepth();
if (w < 0 || h < 0 || d < 0) {
return bounds.makeEmpty();
}
final float hw = w * 0.5f;
final float hh = h * 0.5f;
final float hd = d * 0.5f;
bounds = bounds.deriveWithNewBounds(-hw, -hh, -hd, hw, hh, hd);
bounds = tx.transform(bounds, bounds);
return bounds;
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private boolean doComputeContains(double localX, double localY) {
double w = getWidth();
double h = getHeight();
return -w <= localX && localX <= w &&
-h <= localY && localY <= h;
}
/*
* Note: This method MUST only be called via its accessor method.
*/
private boolean doComputeIntersects(PickRay pickRay, PickResultChooser pickResult) {
final double w = getWidth();
final double h = getHeight();
final double d = getDepth();
final double hWidth = w / 2.0;
final double hHeight = h / 2.0;
final double hDepth = d / 2.0;
final Vec3d dir = pickRay.getDirectionNoClone();
final double invDirX = dir.x == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.x);
final double invDirY = dir.y == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.y);
final double invDirZ = dir.z == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.z);
final Vec3d origin = pickRay.getOriginNoClone();
final double originX = origin.x;
final double originY = origin.y;
final double originZ = origin.z;
final boolean signX = invDirX < 0.0;
final boolean signY = invDirY < 0.0;
final boolean signZ = invDirZ < 0.0;
double t0 = Double.NEGATIVE_INFINITY;
double t1 = Double.POSITIVE_INFINITY;
char side0 = '0';
char side1 = '0';
if (Double.isInfinite(invDirX)) {
if (-hWidth <= originX && hWidth >= originX) {
// move on, we are inside for the whole length
} else {
return false;
}
} else {
t0 = ((signX ? hWidth : -hWidth) - originX) * invDirX;
t1 = ((signX ? -hWidth : hWidth) - originX) * invDirX;
side0 = signX ? 'X' : 'x';
side1 = signX ? 'x' : 'X';
}
if (Double.isInfinite(invDirY)) {
if (-hHeight <= originY && hHeight >= originY) {
// move on, we are inside for the whole length
} else {
return false;
}
} else {
final double ty0 = ((signY ? hHeight : -hHeight) - originY) * invDirY;
final double ty1 = ((signY ? -hHeight : hHeight) - originY) * invDirY;
if ((t0 > ty1) || (ty0 > t1)) {
return false;
}
if (ty0 > t0) {
side0 = signY ? 'Y' : 'y';
t0 = ty0;
}
if (ty1 < t1) {
side1 = signY ? 'y' : 'Y';
t1 = ty1;
}
}
if (Double.isInfinite(invDirZ)) {
if (-hDepth <= originZ && hDepth >= originZ) {
// move on, we are inside for the whole length
} else {
return false;
}
} else {
double tz0 = ((signZ ? hDepth : -hDepth) - originZ) * invDirZ;
double tz1 = ((signZ ? -hDepth : hDepth) - originZ) * invDirZ;
if ((t0 > tz1) || (tz0 > t1)) {
return false;
}
if (tz0 > t0) {
side0 = signZ ? 'Z' : 'z';
t0 = tz0;
}
if (tz1 < t1) {
side1 = signZ ? 'z' : 'Z';
t1 = tz1;
}
}
char side = side0;
double t = t0;
final CullFace cullFace = getCullFace();
final double minDistance = pickRay.getNearClip();
final double maxDistance = pickRay.getFarClip();
if (t0 > maxDistance) {
return false;
}
if (t0 < minDistance || cullFace == CullFace.FRONT) {
if (t1 >= minDistance && t1 <= maxDistance && cullFace != CullFace.BACK) {
side = side1;
t = t1;
} else {
return false;
}
}
if (Double.isInfinite(t) || Double.isNaN(t)) {
// We've got a nonsense pick ray or box size.
return false;
}
if (pickResult != null && pickResult.isCloser(t)) {
Point3D point = PickResultChooser.computePoint(pickRay, t);
Point2D txtCoords = null;
switch (side) {
case 'x': // left
txtCoords = new Point2D(
0.5 - point.getZ() / d,
0.5 + point.getY() / h);
break;
case 'X': // right
txtCoords = new Point2D(
0.5 + point.getZ() / d,
0.5 + point.getY() / h);
break;
case 'y': // top
txtCoords = new Point2D(
0.5 + point.getX() / w,
0.5 - point.getZ() / d);
break;
case 'Y': // bottom
txtCoords = new Point2D(
0.5 + point.getX() / w,
0.5 + point.getZ() / d);
break;
case 'z': // front
txtCoords = new Point2D(
0.5 + point.getX() / w,
0.5 + point.getY() / h);
break;
case 'Z': // back
txtCoords = new Point2D(
0.5 - point.getX() / w,
0.5 + point.getY() / h);
break;
default:
// No hit with any of the planes. We must have had a zero
// pick ray direction vector. Should never happen.
return false;
}
pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txtCoords);
}
return true;
}
static TriangleMesh createMesh(float w, float h, float d) {
// NOTE: still create mesh for degenerated box
float hw = w / 2f;
float hh = h / 2f;
float hd = d / 2f;
float points[] = {
-hw, -hh, -hd,
hw, -hh, -hd,
hw, hh, -hd,
-hw, hh, -hd,
-hw, -hh, hd,
hw, -hh, hd,
hw, hh, hd,
-hw, hh, hd};
float texCoords[] = {0, 0, 1, 0, 1, 1, 0, 1};
// Specifies hard edges.
int faceSmoothingGroups[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
int faces[] = {
0, 0, 2, 2, 1, 1,
2, 2, 0, 0, 3, 3,
1, 0, 6, 2, 5, 1,
6, 2, 1, 0, 2, 3,
5, 0, 7, 2, 4, 1,
7, 2, 5, 0, 6, 3,
4, 0, 3, 2, 0, 1,
3, 2, 4, 0, 7, 3,
3, 0, 6, 2, 2, 1,
6, 2, 3, 0, 7, 3,
4, 0, 1, 2, 5, 1,
1, 2, 4, 0, 0, 3,
};
TriangleMesh mesh = new TriangleMesh(true);
mesh.getPoints().setAll(points);
mesh.getTexCoords().setAll(texCoords);
mesh.getFaces().setAll(faces);
mesh.getFaceSmoothingGroups().setAll(faceSmoothingGroups);
return mesh;
}
private static class BoxKey extends Key {
final double width, height, depth;
private BoxKey(double width, double height, double depth) {
this.width = width;
this.height = height;
this.depth = depth;
}
@Override
public int hashCode() {
long bits = 7L;
bits = 31L * bits + Double.doubleToLongBits(depth);
bits = 31L * bits + Double.doubleToLongBits(height);
bits = 31L * bits + Double.doubleToLongBits(width);
return Long.hashCode(bits);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof BoxKey)) {
return false;
}
BoxKey other = (BoxKey) obj;
if (Double.compare(depth, other.depth) != 0) {
return false;
}
if (Double.compare(height, other.height) != 0) {
return false;
}
if (Double.compare(width, other.width) != 0) {
return false;
}
return true;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy