package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
/**
* This class represents the Face's indexes loop. It is a simplified implementation of directed graph.
*
* @author Marcin Roguski (Kaelthas)
*/
public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
public static final IndexPredicate INDEX_PREDICATE_USE_ALL = new IndexPredicate() {
@Override
public boolean execute(Integer index) {
return true;
}
};
/** The indexes. */
private List<Integer> nodes;
/** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */
private Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
/**
* The constructor uses the given nodes in their give order. Each neighbour indexes will form an edge.
* @param nodes
* the nodes for the loop
*/
public IndexesLoop(Integer[] nodes) {
this.nodes = new ArrayList<Integer>(Arrays.asList(nodes));
this.prepareEdges(this.nodes);
}
@Override
public IndexesLoop clone() {
return new IndexesLoop(nodes.toArray(new Integer[nodes.size()]));
}
/**
* The method prepares edges for the given indexes.
* @param nodes
* the indexes
*/
private void prepareEdges(List<Integer> nodes) {
for (int i = 0; i < nodes.size() - 1; ++i) {
if (edges.containsKey(nodes.get(i))) {
edges.get(nodes.get(i)).add(nodes.get(i + 1));
} else {
edges.put(nodes.get(i), new ArrayList<Integer>(Arrays.asList(nodes.get(i + 1))));
}
}
edges.put(nodes.get(nodes.size() - 1), new ArrayList<Integer>(Arrays.asList(nodes.get(0))));
}
/**
* @return the count of indexes
*/
public int size() {
return nodes.size();
}
/**
* Adds edge to the loop.
* @param from
* the start index
* @param to
* the end index
*/
public void addEdge(Integer from, Integer to) {
if (nodes.contains(from) && nodes.contains(to)) {
if (edges.containsKey(from) && !edges.get(from).contains(to)) {
edges.get(from).add(to);
}
}
}
/**
* Removes edge from the face. The edge is removed if it already exists in the face.
* @param node1
* the first index of the edge to be removed
* @param node2
* the second index of the edge to be removed
* @return <b>true</b> if the edge was removed and <b>false</b> otherwise
*/
public boolean removeEdge(Integer node1, Integer node2) {
boolean edgeRemoved = false;
if (nodes.contains(node1) && nodes.contains(node2)) {
if (edges.containsKey(node1)) {
edgeRemoved |= edges.get(node1).remove(node2);
}
if (edges.containsKey(node2)) {
edgeRemoved |= edges.get(node2).remove(node1);
}
if (edgeRemoved) {
if (this.getNeighbourCount(node1) == 0) {
this.removeIndexes(node1);
}
if (this.getNeighbourCount(node2) == 0) {
this.removeIndexes(node2);
}
}
}
return edgeRemoved;
}
/**
* Tells if the given indexes are neighbours.
* @param index1
* the first index
* @param index2
* the second index
* @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise
*/
public boolean areNeighbours(Integer index1, Integer index2) {
if (index1.equals(index2) || !edges.containsKey(index1) || !edges.containsKey(index2)) {
return false;
}
return edges.get(index1).contains(index2) || edges.get(index2).contains(index1);
}
/**
* Returns the value of the index located after the given one. Pointint the last index will return the first one.
* @param index
* the index value
* @return the value of 'next' index
*/
public Integer getNextIndex(Integer index) {
int i = nodes.indexOf(index);
return i == nodes.size() - 1 ? nodes.get(0) : nodes.get(i + 1);
}
/**
* Returns the value of the index located before the given one. Pointint the first index will return the last one.
* @param index
* the index value
* @return the value of 'previous' index
*/
public Integer getPreviousIndex(Integer index) {
int i = nodes.indexOf(index);
return i == 0 ? nodes.get(nodes.size() - 1) : nodes.get(i - 1);
}
/**
* The method shifts all indexes by a given value.
* @param shift
* the value to shift all indexes
* @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) {
predicate = INDEX_PREDICATE_USE_ALL;
}
List<Integer> nodes = new ArrayList<Integer>(this.nodes.size());
for (Integer node : this.nodes) {
nodes.add(node + (predicate.execute(node) ? shift : 0));
}
Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
for (Entry<Integer, List<Integer>> entry : this.edges.entrySet()) {
List<Integer> neighbours = new ArrayList<Integer>(entry.getValue().size());
for (Integer neighbour : entry.getValue()) {
neighbours.add(neighbour + (predicate.execute(neighbour) ? shift : 0));
}
edges.put(entry.getKey() + shift, neighbours);
}
this.nodes = nodes;
this.edges = edges;
}
/**
* Reverses the order of the indexes.
*/
public void reverse() {
Collections.reverse(nodes);
edges.clear();
this.prepareEdges(nodes);
}
/**
* Returns the neighbour count of the given index.
* @param index
* the index whose neighbour count will be checked
* @return the count of neighbours of the given index
*/
private int getNeighbourCount(Integer index) {
int result = 0;
if (edges.containsKey(index)) {
result = edges.get(index).size();
for (List<Integer> neighbours : edges.values()) {
if (neighbours.contains(index)) {
++result;
}
}
}
return result;
}
/**
* Returns the position of the given index in the loop.
* @param index
* the index of the face
* @return the indexe's position in the loop
*/
public int indexOf(Integer index) {
return nodes.indexOf(index);
}
/**
* Returns the index at the given position.
* @param indexPosition
* the position of the index
* @return the index at a given position
*/
public Integer get(int indexPosition) {
return nodes.get(indexPosition);
}
/**
* @return all indexes of the face
*/
public List<Integer> getAll() {
return new ArrayList<Integer>(nodes);
}
/**
* The method removes all given indexes.
* @param indexes
* the indexes to be removed
*/
public void removeIndexes(Integer... indexes) {
for (Integer index : indexes) {
nodes.remove(index);
edges.remove(index);
for (List<Integer> neighbours : edges.values()) {
neighbours.remove(index);
}
}
}
/**
* The method finds the path between the given indexes.
* @param start
* the start index
* @param end
* the end index
* @param result
* a list containing indexes on the path from start to end (inclusive)
* @throws IllegalStateException
* an exception is thrown when the loop is not normalized (at least one
* index has more than 2 neighbours)
* @throws BlenderFileException
* an exception is thrown if the vertices of a face create more than one loop; this is thrown
* to prevent lack of memory errors during triangulation
*/
public void findPath(Integer start, Integer end, List<Integer> result) throws BlenderFileException {
result.clear();
Integer node = start;
while (!node.equals(end)) {
if (result.contains(node)) {
throw new BlenderFileException("Indexes of face have infinite loops!");
}
result.add(node);
List<Integer> nextSteps = edges.get(node);
if (nextSteps == null || nextSteps.size() == 0) {
result.clear();// no directed path from start to end
return;
} else if (nextSteps.size() == 1) {
node = nextSteps.get(0);
} else {
throw new BlenderFileException("Triangulation failed. Face has ambiguous indexes loop. Please triangulate your model in Blender as a workaround.");
}
}
result.add(end);
}
@Override
public String toString() {
return "IndexesLoop " + nodes.toString();
}
@Override
public int compare(Integer i1, Integer i2) {
return nodes.indexOf(i1) - nodes.indexOf(i2);
}
@Override
public Iterator<Integer> iterator() {
return nodes.iterator();
}
public static interface IndexPredicate {
boolean execute(Integer index);
}
}