/*
* Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.shape;
import com.sun.javafx.scene.shape.
ObservableFaceArrayImpl;
import com.sun.javafx.collections.
FloatArraySyncer;
import com.sun.javafx.collections.
IntegerArraySyncer;
import com.sun.javafx.geom.
BaseBounds;
import com.sun.javafx.geom.
BoxBounds;
import com.sun.javafx.geom.
PickRay;
import com.sun.javafx.geom.
Vec3d;
import com.sun.javafx.scene.input.
PickResultChooser;
import com.sun.javafx.sg.prism.
NGTriangleMesh;
import javafx.beans.property.
ObjectProperty;
import javafx.beans.property.
SimpleObjectProperty;
import javafx.collections.
ArrayChangeListener;
import javafx.collections.
FXCollections;
import javafx.collections.
ObservableArray;
import javafx.collections.
ObservableFloatArray;
import javafx.collections.
ObservableIntegerArray;
import javafx.geometry.
Point2D;
import javafx.geometry.
Point3D;
import javafx.scene.
Node;
import javafx.scene.input.
PickResult;
import javafx.scene.transform.
Affine;
import javafx.scene.transform.
NonInvertibleTransformException;
import javafx.scene.transform.
Rotate;
import sun.util.logging.
PlatformLogger;
/**
* Defines a 3D triangle mesh that consists of its associated {@code VertexFormat}
* and a set of separate arrays of vertex components such as points, normals,
* texture coordinates, and an array of faces that define the individual triangles
* of the mesh.
*<p>
* Note that the term point, as used in the method names and method
* descriptions, actually refers to a 3D point (x, y, z) in space
* representing the position of a single vertex. The term points (plural) is
* used to indicate sets of 3D points for multiple vertices.
* Similarly, the term normal is used to indicate a 3D vector (nx, ny, nz) in space
* representing the direction of a single vertex. The term normals (plural) is
* used to indicate sets of 3D vectors for multiple vertices.
* The term texCoord is used to indicate a single pair of 2D texture
* coordinates (u, v) for a single vertex, while the term texCoords (plural) is used
* to indicate sets of texture coordinates for multiple vertices.
* Lastly, the term face is used to indicate 3 set of interleaving points,
* normals (optional, depending on the associated VertexFormat)
* and texture coordinates that together represent the geometric topology of a
* single triangle, while the term faces (plural) is used to indicate sets of
* triangles (each represent by a face).
* <p>
* For example, the faces with {@code VertexFormat.POINT_TEXCOORD} that represent
* a single textured rectangle, using 2 triangles, have the following data order: [
* <p>
* p0, t0, p1, t1, p3, t3, // First triangle of a textured rectangle
* <p>
* p1, t1, p2, t2, p3, t3 // Second triangle of a textured rectangle
* <p>
* ]
* <p>
* whereas the faces with {@code VertexFormat.POINT_NORMAL_TEXCOORD} that represent
* a single textured rectangle, using 2 triangles, have the following data order: [
* <p>
* p0, n0, t0, p1, n1, t1, p3, n3, t3, // First triangle of a textured rectangle
* <p>
* p1, n1, t1, p2, n2, t2, p3, n3, t3 // Second triangle of a textured rectangle
* <p>
* ]
* <p>
* where p0, p1, p2 and p3 are indices into the points array, n0, n1, n2 and n3
* are indices into the normals array, and t0, t1, t2 and t3 are indices into
* the texCoords array.
*
* <p>
* A triangle has a front and back face. The winding order of a triangle's vertices
* determines which side is the front face. JavaFX chooses the counter-clockwise
* (or right-hand rule) winding order as the front face. By default, only the
* front face of a triangle is rendered. See {@code CullFace} for more
* information.
*
* <p>
* The length of {@code points}, {@code normals}, and {@code texCoords} must be
* divisible by 3, 3, and 2 respectively. The length of {@code faces} must be
* divisible by 6 if it is of {@code VertexFormat.POINT_TEXCOORD} else it must
* be divisible by 9 if it is of {@code VertexFormat.POINT_NORMAL_TEXCOORD}.
* The values in the faces array must be within the range of the number of vertices
* in the points array (0 to points.length / 3 - 1) for the point indices, within
* the range of the number of vertices in the normals array
* (0 to normals.length / 3 - 1) for the normal indices, and within the range of
* the number of the vertices in the texCoords array (0 to texCoords.length / 2 - 1)
* for the texture coordinate indices.
*
* <p> A warning will be recorded to the logger and the mesh will not be rendered
* (and will have an empty bounds) if any of the array lengths are invalid
* or if any of the values in the faces array are out of range.
*
* @since JavaFX 8.0
*/
public class
TriangleMesh extends
Mesh {
private final
ObservableFloatArray points =
FXCollections.
observableFloatArray();
private final
ObservableFloatArray normals =
FXCollections.
observableFloatArray();
private final
ObservableFloatArray texCoords =
FXCollections.
observableFloatArray();
private final
ObservableFaceArray faces = new
ObservableFaceArrayImpl();
private final
ObservableIntegerArray faceSmoothingGroups =
FXCollections.
observableIntegerArray();
private final
Listener pointsSyncer = new
Listener(
points);
private final
Listener normalsSyncer = new
Listener(
normals);
private final
Listener texCoordsSyncer = new
Listener(
texCoords);
private final
Listener facesSyncer = new
Listener(
faces);
private final
Listener faceSmoothingGroupsSyncer = new
Listener(
faceSmoothingGroups);
private final boolean
isPredefinedShape;
private boolean
isValidDirty = true;
private boolean
isPointsValid,
isNormalsValid,
isTexCoordsValid,
isFacesValid,
isFaceSmoothingGroupValid;
private int
refCount = 1;
private
BaseBounds cachedBounds;
/**
* Creates a new instance of {@code TriangleMesh} class with the default
* {@code VertexFormat.POINT_TEXCOORD} format type.
*/
public
TriangleMesh() {
this(false);
}
/**
* Creates a new instance of {@code TriangleMesh} class with the specified
* {@code VertexFormat}.
*
* @param vertexFormat specifies the vertex format type.
*
* @since JavaFX 8u40
*/
public
TriangleMesh(
VertexFormat vertexFormat) {
this(false);
this.
setVertexFormat(
vertexFormat);
}
TriangleMesh(boolean
isPredefinedShape) {
this.
isPredefinedShape =
isPredefinedShape;
if (
isPredefinedShape) {
isPointsValid = true;
isNormalsValid = true;
isTexCoordsValid = true;
isFacesValid = true;
isFaceSmoothingGroupValid = true;
} else {
isPointsValid = false;
isNormalsValid = false;
isTexCoordsValid = false;
isFacesValid = false;
isFaceSmoothingGroupValid = false;
}
}
/**
* Specifies the vertex format of this {@code TriangleMesh}, one of
* {@code VertexFormat.POINT_TEXCOORD} or {@code VertexFormat.POINT_NORMAL_TEXCOORD}.
*
* @defaultValue VertexFormat.POINT_TEXCOORD
*
* @since JavaFX 8u40
*/
private
ObjectProperty<
VertexFormat>
vertexFormat;
public final void
setVertexFormat(
VertexFormat value) {
vertexFormatProperty().
set(
value);
}
public final
VertexFormat getVertexFormat() {
return
vertexFormat == null ?
VertexFormat.
POINT_TEXCOORD :
vertexFormat.
get();
}
public final
ObjectProperty<
VertexFormat>
vertexFormatProperty() {
if (
vertexFormat == null) {
vertexFormat = new
SimpleObjectProperty<
VertexFormat>(
TriangleMesh.this, "vertexFormat") {
@
Override
protected void
invalidated() {
setDirty(true);
// Need to mark faces and faceSmoothingGroups dirty too.
facesSyncer.
setDirty(true);
faceSmoothingGroupsSyncer.
setDirty(true);
}
};
}
return
vertexFormat;
}
/**
* Returns the number of elements that represents a point.
*
* @return number of elements
*/
public final int
getPointElementSize() {
return
getVertexFormat().
getPointElementSize();
}
/**
* Returns the number of elements that represents a normal.
*
* @return number of elements
*
* @since JavaFX 8u40
*/
public final int
getNormalElementSize() {
return
getVertexFormat().
getNormalElementSize();
}
/**
* Returns the number of elements that represents a texture coordinates.
*
* @return number of elements
*/
public final int
getTexCoordElementSize() {
return
getVertexFormat().
getTexCoordElementSize();
}
/**
* Returns the number of elements that represents a face.
*
* @return number of elements
*/
public final int
getFaceElementSize() {
return
getVertexFormat().
getVertexIndexSize() * 3;
}
/**
* Gets the {@code points} array of this {@code TriangleMesh}.
*
* @return {@code points} array where each point is
* represented by 3 float values x, y and z, in that order.
*/
public final
ObservableFloatArray getPoints() {
return
points;
}
/**
* Gets the {@code normals} array of this {@code TriangleMesh}.
*
* @return {@code normals} array where each normal is
* represented by 3 float values nx, ny and nz, in that order.
*
* @since JavaFX 8u40
*/
public final
ObservableFloatArray getNormals() {
return
normals;
}
/**
* Gets the {@code texCoords} array of this {@code TriangleMesh}.
* The coordinates are proportional, so texture's top-left corner
* is at [0, 0] and bottom-right corner is at [1, 1].
*
* @return {@code texCoord} array where each texture coordinate is represented
* by 2 float values: u and v, in that order.
*/
public final
ObservableFloatArray getTexCoords() {
return
texCoords;
}
/**
* Gets the {@code faces} array, indices into the {@code points},
* {@code normals} (optional, if it is a {@code VertexFormat.POINT_NORMAL_TEXCOORD}
* mesh) and {@code texCoords} arrays, of this {@code TriangleMesh}. All
* indices are in terms of elements in to the points, normals or texCoords
* arrays not individual floats.
*
* @return {@code faces} array where each face is of
* 3 * {@code VertexFormat.getVertexIndexSize()} integers.
*/
public final
ObservableFaceArray getFaces() {
return
faces;
}
/**
* Gets the {@code faceSmoothingGroups} array of this {@code TriangleMesh}.
* Smoothing affects how a mesh is rendered but it does not effect its
* geometry. The face smoothing group value is used to control the smoothing
* between adjacent faces.
*
* <p> The face smoothing group value is represented by an array of bits and up to
* 32 unique groups is possible; (1 << 0) to (1 << 31). The face smoothing
* group value can range from 0 (no smoothing group) to all 32 groups. A face
* can belong to zero or more smoothing groups. A face is a member of group
* N if bit N is set, for example, groups |= (1 << N). A value of 0 implies
* no smoothing group or hard edges.
* Smoothing is applied when adjacent pair of faces shared a smoothing group.
* Otherwise the faces are rendered with a hard edge between them.
*
* <p> An empty faceSmoothingGroups implies all faces in this mesh have a
* smoothing group value of 1.
*
* <p> If faceSmoothingGroups is not empty, its size must
* be equal to number of faces.
*
* <p> This faceSmoothingGroups has no effect on its {@code TriangleMesh} if
* it is of {@code VertexFormat.POINT_NORMAL_TEXCOORD} format.
*/
public final
ObservableIntegerArray getFaceSmoothingGroups() {
return
faceSmoothingGroups;
}
@
Override void
setDirty(boolean
value) {
super.setDirty(
value);
if (!
value) { // false
pointsSyncer.
setDirty(false);
normalsSyncer.
setDirty(false);
texCoordsSyncer.
setDirty(false);
facesSyncer.
setDirty(false);
faceSmoothingGroupsSyncer.
setDirty(false);
}
}
int
getRefCount() {
return
refCount;
}
synchronized void
incRef() {
this.
refCount += 1;
}
synchronized void
decRef() {
this.
refCount -= 1;
}
private
NGTriangleMesh peer;
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
/** The peer node created by the graphics Toolkit/Pipeline implementation */
NGTriangleMesh impl_getPGTriangleMesh() {
if (
peer == null) {
peer = new
NGTriangleMesh();
}
return
peer;
}
@
Override
NGTriangleMesh getPGMesh() {
return
impl_getPGTriangleMesh();
}
private boolean
validatePoints() {
if (
points.
size() == 0) { // Valid but meaningless for picking or rendering.
return false;
}
if ((
points.
size() %
getVertexFormat().
getPointElementSize()) != 0) {
String logname =
TriangleMesh.class.
getName();
PlatformLogger.
getLogger(
logname).
warning("points.size() has "
+ "to be divisible by getPointElementSize(). It is to"
+ " store multiple x, y, and z coordinates of this mesh");
return false;
}
return true;
}
private boolean
validateNormals() {
// Only validate normals if vertex format has normal component
if (
getVertexFormat() !=
VertexFormat.
POINT_NORMAL_TEXCOORD) return true;
if (
normals.
size() == 0) { // Valid but meaningless for picking or rendering.
return false;
}
if ((
normals.
size() %
getVertexFormat().
getNormalElementSize()) != 0) {
String logname =
TriangleMesh.class.
getName();
PlatformLogger.
getLogger(
logname).
warning("normals.size() has "
+ "to be divisible by getNormalElementSize(). It is to"
+ " store multiple nx, ny, and nz coordinates of this mesh");
return false;
}
return true;
}
private boolean
validateTexCoords() {
if (
texCoords.
size() == 0) { // Valid but meaningless for picking or rendering.
return false;
}
if ((
texCoords.
size() %
getVertexFormat().
getTexCoordElementSize()) != 0) {
String logname =
TriangleMesh.class.
getName();
PlatformLogger.
getLogger(
logname).
warning("texCoords.size() "
+ "has to be divisible by getTexCoordElementSize()."
+ " It is to store multiple u and v texture coordinates"
+ " of this mesh");
return false;
}
return true;
}
private boolean
validateFaces() {
if (
faces.
size() == 0) { // Valid but meaningless for picking or rendering.
return false;
}
String logname =
TriangleMesh.class.
getName();
if ((
faces.
size() %
getFaceElementSize()) != 0) {
PlatformLogger.
getLogger(
logname).
warning("faces.size() has "
+ "to be divisible by getFaceElementSize().");
return false;
}
if (
getVertexFormat() ==
VertexFormat.
POINT_TEXCOORD) {
int
nVerts =
points.
size() /
getVertexFormat().
getPointElementSize();
int
nTVerts =
texCoords.
size() /
getVertexFormat().
getTexCoordElementSize();
for (int
i = 0;
i <
faces.
size();
i++) {
if (
i % 2 == 0 && (
faces.
get(
i) >=
nVerts ||
faces.
get(
i) < 0)
|| (
i % 2 != 0 && (
faces.
get(
i) >=
nTVerts ||
faces.
get(
i) < 0))) {
PlatformLogger.
getLogger(
logname).
warning("The values in the "
+ "faces array must be within the range of the number "
+ "of vertices in the points array (0 to points.length / 3 - 1) "
+ "for the point indices and within the range of the "
+ "number of the vertices in the texCoords array (0 to "
+ "texCoords.length / 2 - 1) for the texture coordinate indices.");
return false;
}
}
} else if (
getVertexFormat() ==
VertexFormat.
POINT_NORMAL_TEXCOORD) {
int
nVerts =
points.
size() /
getVertexFormat().
getPointElementSize();
int
nNVerts =
normals.
size() /
getVertexFormat().
getNormalElementSize();
int
nTVerts =
texCoords.
size() /
getVertexFormat().
getTexCoordElementSize();
for (int
i = 0;
i <
faces.
size();
i+=3) {
if ((
faces.
get(
i) >=
nVerts ||
faces.
get(
i) < 0)
|| (
faces.
get(
i + 1) >=
nNVerts ||
faces.
get(
i + 1) < 0)
|| (
faces.
get(
i + 2) >=
nTVerts ||
faces.
get(
i + 2) < 0)) {
PlatformLogger.
getLogger(
logname).
warning("The values in the "
+ "faces array must be within the range of the number "
+ "of vertices in the points array (0 to points.length / 3 - 1) "
+ "for the point indices, and within the range of the "
+ "number of the vertices in the normals array (0 to "
+ "normals.length / 3 - 1) for the normals indices, and "
+ "number of the vertices in the texCoords array (0 to "
+ "texCoords.length / 2 - 1) for the texture coordinate indices.");
return false;
}
}
} else {
PlatformLogger.
getLogger(
logname).
warning("Unsupported VertexFormat: " +
getVertexFormat().
toString());
return false;
}
return true;
}
private boolean
validateFaceSmoothingGroups() {
if (
faceSmoothingGroups.
size() != 0
&&
faceSmoothingGroups.
size() != (
faces.
size() /
getFaceElementSize())) {
String logname =
TriangleMesh.class.
getName();
PlatformLogger.
getLogger(
logname).
warning("faceSmoothingGroups.size()"
+ " has to equal to number of faces.");
return false;
}
return true;
}
private boolean
validate() {
if (
isPredefinedShape) {
return true;
}
if (
isValidDirty) {
if (
pointsSyncer.
dirtyInFull) {
isPointsValid =
validatePoints();
}
if (
normalsSyncer.
dirtyInFull) {
isNormalsValid =
validateNormals();
}
if (
texCoordsSyncer.
dirtyInFull) {
isTexCoordsValid =
validateTexCoords();
}
if (
facesSyncer.
dirty ||
pointsSyncer.
dirtyInFull
||
normalsSyncer.
dirtyInFull ||
texCoordsSyncer.
dirtyInFull) {
isFacesValid =
isPointsValid &&
isNormalsValid
&&
isTexCoordsValid &&
validateFaces();
}
if (
faceSmoothingGroupsSyncer.
dirtyInFull ||
facesSyncer.
dirtyInFull) {
isFaceSmoothingGroupValid =
isFacesValid &&
validateFaceSmoothingGroups();
}
isValidDirty = false;
}
return
isPointsValid &&
isNormalsValid &&
isTexCoordsValid
&&
isFaceSmoothingGroupValid &&
isFacesValid;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
@
Override
void
impl_updatePG() {
if (!
isDirty()) {
return;
}
final
NGTriangleMesh pgTriMesh =
impl_getPGTriangleMesh();
if (
validate()) {
pgTriMesh.
setUserDefinedNormals(
getVertexFormat() ==
VertexFormat.
POINT_NORMAL_TEXCOORD);
pgTriMesh.
syncPoints(
pointsSyncer);
pgTriMesh.
syncNormals(
normalsSyncer);
pgTriMesh.
syncTexCoords(
texCoordsSyncer);
pgTriMesh.
syncFaces(
facesSyncer);
pgTriMesh.
syncFaceSmoothingGroups(
faceSmoothingGroupsSyncer);
} else {
pgTriMesh.
setUserDefinedNormals(false);
pgTriMesh.
syncPoints(null);
pgTriMesh.
syncNormals(null);
pgTriMesh.
syncTexCoords(null);
pgTriMesh.
syncFaces(null);
pgTriMesh.
syncFaceSmoothingGroups(null);
}
setDirty(false);
}
@
Override
BaseBounds computeBounds(
BaseBounds bounds) {
if (
isDirty() ||
cachedBounds == null) {
cachedBounds = new
BoxBounds();
if (
validate()) {
final double
len =
points.
size();
for (int
i = 0;
i <
len;
i +=
getVertexFormat().
getPointElementSize()) {
cachedBounds.
add(
points.
get(
i),
points.
get(
i + 1),
points.
get(
i + 2));
}
}
}
return
bounds.
deriveWithNewBounds(
cachedBounds);
}
/**
* Computes the centroid of the given triangle
* @param v0x x coord of first vertex of the triangle
* @param v0y y coord of first vertex of the triangle
* @param v0z z coord of first vertex of the triangle
* @param v1x x coord of second vertex of the triangle
* @param v1y y coord of second vertex of the triangle
* @param v1z z coord of second vertex of the triangle
* @param v2x x coord of third vertex of the triangle
* @param v2y y coord of third vertex of the triangle
* @param v2z z coord of third vertex of the triangle
* @return the triangle centroid
*/
private
Point3D computeCentroid(
double
v0x, double
v0y, double
v0z,
double
v1x, double
v1y, double
v1z,
double
v2x, double
v2y, double
v2z) {
// Point3D center = v1.midpoint(v2);
// Point3D vec = center.subtract(v0);
// return v0.add(new Point3D(vec.getX() / 3.0, vec.getY() / 3.0, vec.getZ() / 3.0));
return new
Point3D(
v0x + (
v2x + (
v1x -
v2x) / 2.0 -
v0x) / 3.0,
v0y + (
v2y + (
v1y -
v2y) / 2.0 -
v0y) / 3.0,
v0z + (
v2z + (
v1z -
v2z) / 2.0 -
v0z) / 3.0);
}
/**
* Computes the centroid of the given triangle
* @param v0 vertex of the triangle
* @param v1 vertex of the triangle
* @param v2 vertex of the triangle
* @return the triangle centroid
*/
private
Point2D computeCentroid(
Point2D v0,
Point2D v1,
Point2D v2) {
Point2D center =
v1.
midpoint(
v2);
Point2D vec =
center.
subtract(
v0);
return
v0.
add(new
Point2D(
vec.
getX() / 3.0,
vec.
getY() / 3.0));
}
/**
* Computes intersection of a pick ray and a single triangle face.
*
* It takes pickRay, origin and dir. The latter two can be of course obtained
* from the pickRay, but we need them to be converted to Point3D and don't
* want to do that for all faces. Therefore the conversion is done just once
* and passed to the method for all the faces.
*
* @param pickRay pick ray
* @param origin pick ray's origin
* @param dir pick ray's direction
* @param faceIndex index of the face to test
* @param cullFace cull face of the Node (and thus the tested face)
* @param candidate the owner node (for the possible placement to the result)
* @param reportFace whether or not to report he hit face
* @param result the pick result to be updated if a closer intersection is found
* @return true if the pick ray intersects with the face (regardless of whether
* the result has been updated)
*/
private boolean
computeIntersectsFace(
PickRay pickRay,
Vec3d origin,
Vec3d dir, int
faceIndex,
CullFace cullFace,
Node candidate, boolean
reportFace,
PickResultChooser result) {//, BoxBounds rayBounds) {
// This computation was naturally done by Point3D and its operations,
// but it needs a lot of points and there is often a lot of triangles
// so it is vital for performance to use only primitive variables
// and do the computing manually.
int
vertexIndexSize =
getVertexFormat().
getVertexIndexSize();
int
pointElementSize =
getVertexFormat().
getPointElementSize();
final int
v0Idx =
faces.
get(
faceIndex) *
pointElementSize;
final int
v1Idx =
faces.
get(
faceIndex +
vertexIndexSize) *
pointElementSize;
final int
v2Idx =
faces.
get(
faceIndex + (2 *
vertexIndexSize)) *
pointElementSize;
final float
v0x =
points.
get(
v0Idx);
final float
v0y =
points.
get(
v0Idx + 1);
final float
v0z =
points.
get(
v0Idx + 2);
final float
v1x =
points.
get(
v1Idx);
final float
v1y =
points.
get(
v1Idx + 1);
final float
v1z =
points.
get(
v1Idx + 2);
final float
v2x =
points.
get(
v2Idx);
final float
v2y =
points.
get(
v2Idx + 1);
final float
v2z =
points.
get(
v2Idx + 2);
// e1 = v1.subtract(v0)
final float
e1x =
v1x -
v0x;
final float
e1y =
v1y -
v0y;
final float
e1z =
v1z -
v0z;
// e2 = v2.subtract(v0)
final float
e2x =
v2x -
v0x;
final float
e2y =
v2y -
v0y;
final float
e2z =
v2z -
v0z;
// h = dir.crossProduct(e2)
final double
hx =
dir.
y *
e2z -
dir.
z *
e2y;
final double
hy =
dir.
z *
e2x -
dir.
x *
e2z;
final double
hz =
dir.
x *
e2y -
dir.
y *
e2x;
// a = e1.dotProduct(h)
final double
a =
e1x *
hx +
e1y *
hy +
e1z *
hz;
if (
a == 0.0) {
return false;
}
final double
f = 1.0 /
a;
// s = origin.subtract(v0)
final double
sx =
origin.
x -
v0x;
final double
sy =
origin.
y -
v0y;
final double
sz =
origin.
z -
v0z;
// u = f * (s.dotProduct(h))
final double
u =
f * (
sx *
hx +
sy *
hy +
sz *
hz);
if (
u < 0.0 ||
u > 1.0) {
return false;
}
// q = s.crossProduct(e1)
final double
qx =
sy *
e1z -
sz *
e1y;
final double
qy =
sz *
e1x -
sx *
e1z;
final double
qz =
sx *
e1y -
sy *
e1x;
// v = f * dir.dotProduct(q)
double
v =
f * (
dir.
x *
qx +
dir.
y *
qy +
dir.
z *
qz);
if (
v < 0.0 ||
u +
v > 1.0) {
return false;
}
// t = f * e2.dotProduct(q)
final double
t =
f * (
e2x *
qx +
e2y *
qy +
e2z *
qz);
if (
t >=
pickRay.
getNearClip() &&
t <=
pickRay.
getFarClip()) {
// This branch is entered only for hit triangles (not so often),
// so we can get smoothly back to the nice code using Point3Ds.
if (
cullFace !=
CullFace.
NONE) {
// normal = e1.crossProduct(e2)
final
Point3D normal = new
Point3D(
e1y *
e2z -
e1z *
e2y,
e1z *
e2x -
e1x *
e2z,
e1x *
e2y -
e1y *
e2x);
final double
nangle =
normal.
angle(
new
Point3D(-
dir.
x, -
dir.
y, -
dir.
z));
if ((
nangle >= 90 ||
cullFace !=
CullFace.
BACK) &&
(
nangle <= 90 ||
cullFace !=
CullFace.
FRONT)) {
// hit culled face
return false;
}
}
if (
Double.
isInfinite(
t) ||
Double.
isNaN(
t)) {
// we've got a nonsense pick ray or triangle
return false;
}
if (
result == null || !
result.
isCloser(
t)) {
// it intersects, but we are not interested in the result
// or we already have a better (closer) result
// so we can omit the point and texture computation
return true;
}
Point3D point =
PickResultChooser.
computePoint(
pickRay,
t);
// Now compute texture mapping. First rotate the triangle
// so that we can compute in 2D
// centroid = computeCentroid(v0, v1, v2);
final
Point3D centroid =
computeCentroid(
v0x,
v0y,
v0z,
v1x,
v1y,
v1z,
v2x,
v2y,
v2z);
// cv0 = v0.subtract(centroid)
final
Point3D cv0 = new
Point3D(
v0x -
centroid.
getX(),
v0y -
centroid.
getY(),
v0z -
centroid.
getZ());
// cv1 = v1.subtract(centroid)
final
Point3D cv1 = new
Point3D(
v1x -
centroid.
getX(),
v1y -
centroid.
getY(),
v1z -
centroid.
getZ());
// cv2 = v2.subtract(centroid)
final
Point3D cv2 = new
Point3D(
v2x -
centroid.
getX(),
v2y -
centroid.
getY(),
v2z -
centroid.
getZ());
final
Point3D ce1 =
cv1.
subtract(
cv0);
final
Point3D ce2 =
cv2.
subtract(
cv0);
Point3D n =
ce1.
crossProduct(
ce2);
if (
n.
getZ() < 0) {
n = new
Point3D(-
n.
getX(), -
n.
getY(), -
n.
getZ());
}
final
Point3D ax =
n.
crossProduct(
Rotate.
Z_AXIS);
final double
angle =
Math.
atan2(
ax.
magnitude(),
n.
dotProduct(
Rotate.
Z_AXIS));
Rotate r = new
Rotate(
Math.
toDegrees(
angle),
ax);
final
Point3D crv0 =
r.
transform(
cv0);
final
Point3D crv1 =
r.
transform(
cv1);
final
Point3D crv2 =
r.
transform(
cv2);
final
Point3D rPoint =
r.
transform(
point.
subtract(
centroid));
final
Point2D flatV0 = new
Point2D(
crv0.
getX(),
crv0.
getY());
final
Point2D flatV1 = new
Point2D(
crv1.
getX(),
crv1.
getY());
final
Point2D flatV2 = new
Point2D(
crv2.
getX(),
crv2.
getY());
final
Point2D flatPoint = new
Point2D(
rPoint.
getX(),
rPoint.
getY());
// Obtain the texture triangle
int
texCoordElementSize =
getVertexFormat().
getTexCoordElementSize();
int
texCoordOffset =
getVertexFormat().
getTexCoordIndexOffset();
final int
t0Idx =
faces.
get(
faceIndex +
texCoordOffset) *
texCoordElementSize;
final int
t1Idx =
faces.
get(
faceIndex +
vertexIndexSize +
texCoordOffset) *
texCoordElementSize;
final int
t2Idx =
faces.
get(
faceIndex + (
vertexIndexSize * 2) +
texCoordOffset) *
texCoordElementSize;
final
Point2D u0 = new
Point2D(
texCoords.
get(
t0Idx),
texCoords.
get(
t0Idx + 1));
final
Point2D u1 = new
Point2D(
texCoords.
get(
t1Idx),
texCoords.
get(
t1Idx + 1));
final
Point2D u2 = new
Point2D(
texCoords.
get(
t2Idx),
texCoords.
get(
t2Idx + 1));
final
Point2D txCentroid =
computeCentroid(
u0,
u1,
u2);
final
Point2D cu0 =
u0.
subtract(
txCentroid);
final
Point2D cu1 =
u1.
subtract(
txCentroid);
final
Point2D cu2 =
u2.
subtract(
txCentroid);
// Find the transform between the two triangles
final
Affine src = new
Affine(
flatV0.
getX(),
flatV1.
getX(),
flatV2.
getX(),
flatV0.
getY(),
flatV1.
getY(),
flatV2.
getY());
final
Affine trg = new
Affine(
cu0.
getX(),
cu1.
getX(),
cu2.
getX(),
cu0.
getY(),
cu1.
getY(),
cu2.
getY());
Point2D txCoords = null;
try {
src.
invert();
trg.
append(
src);
txCoords =
txCentroid.
add(
trg.
transform(
flatPoint));
} catch (
NonInvertibleTransformException e) {
// Can't compute texture mapping, probably the coordinates
// don't make sense. Ignore it and return null tex coords.
}
result.
offer(
candidate,
t,
reportFace ?
faceIndex /
getFaceElementSize() :
PickResult.
FACE_UNDEFINED,
point,
txCoords);
return true;
}
return false;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Override
@
Deprecated
protected boolean
impl_computeIntersects(
PickRay pickRay,
PickResultChooser pickResult,
Node candidate,
CullFace cullFace, boolean
reportFace) {
boolean
found = false;
if (
validate()) {
final int
size =
faces.
size();
final
Vec3d o =
pickRay.
getOriginNoClone();
final
Vec3d d =
pickRay.
getDirectionNoClone();
for (int
i = 0;
i <
size;
i +=
getFaceElementSize()) {
if (
computeIntersectsFace(
pickRay,
o,
d,
i,
cullFace,
candidate,
reportFace,
pickResult)) {
found = true;
}
}
}
return
found;
}
private class
Listener<T extends
ObservableArray<T>> implements
ArrayChangeListener<T>,
FloatArraySyncer,
IntegerArraySyncer {
protected final T
array;
protected boolean
dirty = true;
/**
* Array was replaced
* @return true if array was replaced; false otherwise
*/
protected boolean
dirtyInFull = true;
protected int
dirtyRangeFrom;
protected int
dirtyRangeLength;
public
Listener(T
array) {
this.
array =
array;
array.
addListener(this);
}
/**
* Adds a dirty range
* @param from index of the first modified element
* @param length length of the modified range
*/
protected final void
addDirtyRange(int
from, int
length) {
if (
length > 0 && !
dirtyInFull) {
markDirty();
if (
dirtyRangeLength == 0) {
dirtyRangeFrom =
from;
dirtyRangeLength =
length;
} else {
int
fromIndex =
Math.
min(
dirtyRangeFrom,
from);
int
toIndex =
Math.
max(
dirtyRangeFrom +
dirtyRangeLength,
from +
length);
dirtyRangeFrom =
fromIndex;
dirtyRangeLength =
toIndex -
fromIndex;
}
}
}
protected void
markDirty() {
dirty = true;
TriangleMesh.this.
setDirty(true);
}
@
Override
public void
onChanged(T
observableArray, boolean
sizeChanged, int
from, int
to) {
if (
sizeChanged) {
setDirty(true);
} else {
addDirtyRange(
from,
to -
from);
}
isValidDirty = true;
}
/**
* @param dirty if true, the whole collection is marked as dirty;
* if false, the whole collection is marked as not-dirty
*/
public final void
setDirty(boolean
dirty) {
this.
dirtyInFull =
dirty;
if (
dirty) {
markDirty();
dirtyRangeFrom = 0;
dirtyRangeLength =
array.
size();
} else {
this.
dirty = false;
dirtyRangeFrom =
dirtyRangeLength = 0;
}
}
@
Override
public float[]
syncTo(float[]
array, int[]
fromAndLengthIndices) {
assert ((
fromAndLengthIndices != null) && (
fromAndLengthIndices.length == 2));
ObservableFloatArray floatArray = (
ObservableFloatArray) this.
array;
if (
dirtyInFull ||
array == null ||
array.length !=
floatArray.
size()) {
// Always allocate a new array when size changes
fromAndLengthIndices[0] = 0;
fromAndLengthIndices[1] =
floatArray.
size();
return
floatArray.
toArray(null);
}
fromAndLengthIndices[0] =
dirtyRangeFrom;
fromAndLengthIndices[1] =
dirtyRangeLength;
floatArray.
copyTo(
dirtyRangeFrom,
array,
dirtyRangeFrom,
dirtyRangeLength);
return
array;
}
@
Override
public int[]
syncTo(int[]
array, int[]
fromAndLengthIndices) {
assert ((
fromAndLengthIndices != null) && (
fromAndLengthIndices.length == 2));
ObservableIntegerArray intArray = (
ObservableIntegerArray) this.
array;
if (
dirtyInFull ||
array == null ||
array.length !=
intArray.
size()) {
fromAndLengthIndices[0] = 0;
fromAndLengthIndices[1] =
intArray.
size();
// Always allocate a new array when size changes
return
intArray.
toArray(null);
}
fromAndLengthIndices[0] =
dirtyRangeFrom;
fromAndLengthIndices[1] =
dirtyRangeLength;
intArray.
copyTo(
dirtyRangeFrom,
array,
dirtyRangeFrom,
dirtyRangeLength);
return
array;
}
}
}