com.jme3.scene.plugins.blender.meshes.Edge Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jme3-blender Show documentation
Show all versions of jme3-blender Show documentation
jMonkeyEngine is a 3D game engine for adventurous Java developers
The newest version!
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.math.Vector3d;
import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
/**
* A class that represents a single edge between two vertices.
*
* @author Marcin Roguski (Kaelthas)
*/
public class Edge {
private static final Logger LOGGER = Logger.getLogger(Edge.class.getName());
private static final int FLAG_EDGE_NOT_IN_FACE = 0x80;
/** The vertices indexes. */
private int index1, index2;
/** The vertices that can be set if we need and abstract edge outside the mesh (for computations). */
private Vector3f v1, v2;
/** The weight of the edge. */
private float crease;
/** A variable that indicates if this edge belongs to any face or not. */
private boolean inFace;
/** The mesh that owns the edge. */
private TemporalMesh temporalMesh;
public Edge(Vector3f v1, Vector3f v2) {
this.v1 = v1 == null ? new Vector3f() : v1;
this.v2 = v2 == null ? new Vector3f() : v2;
index1 = 0;
index2 = 1;
}
/**
* This constructor only stores the indexes of the vertices. The position vertices should be stored
* outside this class.
* @param index1
* the first index of the edge
* @param index2
* the second index of the edge
* @param crease
* the weight of the face
* @param inFace
* a variable that indicates if this edge belongs to any face or not
*/
public Edge(int index1, int index2, float crease, boolean inFace, TemporalMesh temporalMesh) {
this.index1 = index1;
this.index2 = index2;
this.crease = crease;
this.inFace = inFace;
this.temporalMesh = temporalMesh;
}
@Override
public Edge clone() {
return new Edge(index1, index2, crease, inFace, temporalMesh);
}
/**
* @return the first index of the edge
*/
public int getFirstIndex() {
return index1;
}
/**
* @return the second index of the edge
*/
public int getSecondIndex() {
return index2;
}
/**
* @return the first vertex of the edge
*/
public Vector3f getFirstVertex() {
return temporalMesh == null ? v1 : temporalMesh.getVertices().get(index1);
}
/**
* @return the second vertex of the edge
*/
public Vector3f getSecondVertex() {
return temporalMesh == null ? v2 : temporalMesh.getVertices().get(index2);
}
/**
* Returns the index other than the given.
* @param index
* index of the edge
* @return the remaining index number
*/
public int getOtherIndex(int index) {
if (index == index1) {
return index2;
}
if (index == index2) {
return index1;
}
throw new IllegalArgumentException("Cannot give the other index for [" + index + "] because this index does not exist in edge: " + this);
}
/**
* @return the crease value of the edge (its weight)
*/
public float getCrease() {
return crease;
}
/**
* @return true if the edge is used by at least one face and false otherwise
*/
public boolean isInFace() {
return inFace;
}
/**
* @return the length of the edge
*/
public float getLength() {
return this.getFirstVertex().distance(this.getSecondVertex());
}
/**
* @return the mesh this edge belongs to
*/
public TemporalMesh getTemporalMesh() {
return temporalMesh;
}
/**
* @return the centroid of the edge
*/
public Vector3f computeCentroid() {
return this.getFirstVertex().add(this.getSecondVertex()).divideLocal(2);
}
/**
* Shifts indexes by a given amount.
* @param shift
* how much the indexes should be shifted
* @param predicate
* the predicate that verifies which indexes should be shifted; if null then all will be shifted
*/
public void shiftIndexes(int shift, IndexPredicate predicate) {
if (predicate == null) {
index1 += shift;
index2 += shift;
} else {
index1 += predicate.execute(index1) ? shift : 0;
index2 += predicate.execute(index2) ? shift : 0;
}
}
/**
* Flips the order of the indexes.
*/
public void flipIndexes() {
int temp = index1;
index1 = index2;
index2 = temp;
}
/**
* The crossing method first computes the points on both lines (that contain the edges)
* who are closest in distance. If the distance between points is smaller than FastMath.FLT_EPSILON
* the we consider them to be the same point (the lines cross).
* The second step is to check if both points are contained within the edges.
*
* The method of computing the crossing point is as follows:
* Let's assume that:
* (P0, P1) are the points of the first edge
* (Q0, Q1) are the points of the second edge
*
* u = P1 - P0
* v = Q1 - Q0
*
* This gives us the equations of two lines:
* L1: (x = P1x + ux*t1; y = P1y + uy*t1; z = P1z + uz*t1)
* L2: (x = P2x + vx*t2; y = P2y + vy*t2; z = P2z + vz*t2)
*
* Comparing the x and y of the first two equations for each line will allow us to compute t1 and t2
* (which is implemented below).
* Using t1 and t2 we can compute (x, y, z) of each line and that will give us two points that we need to compare.
*
* @param edge
* the edge we check against crossing
* @return true if the edges cross and false otherwise
*/
public boolean cross(Edge edge) {
return this.getCrossPoint(edge) != null;
}
/**
* The method computes the crossing pint of this edge and another edge. If
* there is no crossing then null is returned.
*
* @param edge
* the edge to compute corss point with
* @return cross point on null if none exist
*/
public Vector3f getCrossPoint(Edge edge) {
return this.getCrossPoint(edge, false, false);
}
/**
* The method computes the crossing pint of this edge and another edge. If
* there is no crossing then null is returned. Also null is returned if the edges are parallel.
* This method also allows to get the crossing point of the straight lines that contain these edges if
* you set the 'extend' parameter to true.
*
* @param edge
* the edge to compute corss point with
* @param extendThisEdge
* set to true to find a crossing point along the whole
* straight that contains the current edge
* @param extendSecondEdge
* set to true to find a crossing point along the whole
* straight that contains the given edge
* @return cross point on null if none exist or the edges are parallel
*/
public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) {
Vector3d P1 = new Vector3d(this.getFirstVertex());
Vector3d P2 = new Vector3d(edge.getFirstVertex());
Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal();
Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal();
if(Math.abs(u.dot(v)) >= 1 - FastMath.DBL_EPSILON) {
// the edges are parallel; do not care about the crossing point
return null;
}
double t1 = 0, t2 = 0;
if(u.x == 0 && v.x == 0) {
t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y);
t1 = (P2.z - P1.z + v.z * t2) / u.z;
} else if(u.y == 0 && v.y == 0) {
t2 = (u.x * (P2.z - P1.z) - u.z * (P2.x - P1.x)) / (u.z * v.x - u.x * v.z);
t1 = (P2.x - P1.x + v.x * t2) / u.x;
} else if(u.z == 0 && v.z == 0) {
t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y);
t1 = (P2.x - P1.x + v.x * t2) / u.x;
} else {
t2 = (P1.y * u.x - P1.x * u.y + P2.x * u.y - P2.y * u.x) / (v.y * u.x - u.y * v.x);
t1 = (P2.x - P1.x + v.x * t2) / u.x;
if(Math.abs(P1.z - P2.z + u.z * t1 - v.z * t2) > FastMath.FLT_EPSILON) {
return null;
}
}
Vector3d p1 = P1.add(u.mult(t1));
Vector3d p2 = P2.add(v.mult(t2));
if (p1.distance(p2) <= FastMath.FLT_EPSILON) {
if(extendThisEdge && extendSecondEdge) {
return p1.toVector3f();
}
// the lines cross, check if p1 and p2 are within the edges
Vector3d p = p1.subtract(P1);
double cos = p.dot(u) / p.length();
if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - this.getLength() <= FastMath.FLT_EPSILON) {
// p1 is inside the first edge, lets check the other edge now
p = p2.subtract(P2);
cos = p.dot(v) / p.length();
if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - edge.getLength() <= FastMath.FLT_EPSILON) {
return p1.toVector3f();
}
}
}
return null;
}
@Override
public String toString() {
String result = "Edge [" + index1 + ", " + index2 + "] {" + crease + "}";
result += " (" + this.getFirstVertex() + " -> " + this.getSecondVertex() + ")";
if (inFace) {
result += "[F]";
}
return result;
}
@Override
public int hashCode() {
// The hash code must be identical for the same two indexes, no matter their order.
final int prime = 31;
int result = 1;
int lowerIndex = Math.min(index1, index2);
int higherIndex = Math.max(index1, index2);
result = prime * result + lowerIndex;
result = prime * result + higherIndex;
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Edge)) {
return false;
}
if (this == obj) {
return true;
}
Edge other = (Edge) obj;
return Math.min(index1, index2) == Math.min(other.index1, other.index2) && Math.max(index1, index2) == Math.max(other.index1, other.index2);
}
/**
* The method loads all edges from the given mesh structure that does not belong to any face.
* @param meshStructure
* the mesh structure
* @param temporalMesh
* the owner of the edges
* @return all edges without faces
* @throws BlenderFileException
* an exception is thrown when problems with file reading occur
*/
public static List loadAll(Structure meshStructure, TemporalMesh temporalMesh) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName());
List result = new ArrayList();
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
if (pMEdge.isNotNull()) {
List edges = pMEdge.fetchData();
for (Structure edge : edges) {
int flag = ((Number) edge.getFieldValue("flag")).intValue();
int v1 = ((Number) edge.getFieldValue("v1")).intValue();
int v2 = ((Number) edge.getFieldValue("v2")).intValue();
// I do not know why, but blender stores (possibly only sometimes) crease as negative values and shows positive in the editor
float crease = Math.abs(((Number) edge.getFieldValue("crease")).floatValue());
boolean edgeInFace = (flag & Edge.FLAG_EDGE_NOT_IN_FACE) == 0;
result.add(new Edge(v1, v2, crease, edgeInFace, temporalMesh));
}
}
LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size());
return result;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy