package bs.bs2d.fea;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import javax.vecmath.Point2f;
/**
* A mesh consisting of 2D triangular (three node) elements.
* @author Djen
*/
public class TriMesh2D {
private final ArrayList<Node2D> nodes;
private final ArrayList<TriElement> elements;
private ArrayList<Node2D> outerNodes;
private ArrayList<Edge> edges, outerEdges;
private Rectangle2D bounds;
private boolean finalized;
public TriMesh2D() {
nodes = new ArrayList<>(300);
elements = new ArrayList<>(1000);
finalized = false;
}
/**
* Adds a node to this mesh. Checks to prevent duplicates.
* @param node the node to be added
*/
public void addNode(Node2D node){
if(finalized){
throw new UnsupportedOperationException("Mesh has already been finalized.");
}
if(!nodes.contains(node))
nodes.add(node);
}
/**
* Constructs a new TriElement from the nodes specified by the given indices
* and adds it to the mesh.
* @param index the global index of the new element
* @param nodeIndex an array containing the indices of three node that have
* previously been added to this mesh
*/
public void addNewElement(int index, int[] nodeIndex){
if(finalized){
throw new UnsupportedOperationException("Mesh has already been finalized.");
}
// check for existing element with same index
if(elements.contains(new TriElement(index)))
return;
Node2D[] elNode = new Node2D[3];
for(int i=0; i<3; i++){
int listIndex = nodes.indexOf(new Node2D(nodeIndex[i]));
if(listIndex != -1){
elNode[i] = nodes.get(listIndex);
} else {
throw new IllegalArgumentException("Node not contained in mesh: " + nodeIndex[i]);
}
}
elements.add(new TriElement(index, elNode));
}
/**
* @param index node index
* @return the node with the given index
*/
public Node2D getNode(int index){
return nodes.get(index);
}
/**
* Finds a node that matches the given coordinates by the given threshold.
* A rectangular match ist used (both coordinates checked independently).
* @param x the x coordinate
* @param y the y coordinate
* @param thrsh the threshold
* @return the first node matching the given coorsinates
*/
public Node2D getNode(float x, float y, float thrsh){
for(Node2D node : nodes){
if(Math.abs(node.getX() - x) < thrsh &&
Math.abs(node.getY() - y) < thrsh){
return node;
}
}
return null;
}
/**
* @return the number of nodes in this mesh
*/
public int getNodeCount(){
return nodes.size();
}
/**
* @return a list of all nodes in this mesh
*/
public List<Node2D> getNodes() {
return nodes;
}
/**
* @param index element index
* @return the element with the given index
*/
public TriElement getElement(int index){
return elements.get(index);
}
public TriElement getElement(Point2D p){
for (TriElement el : elements) {
GeneralPath path = new GeneralPath();
Point2f n0 = el.getNode(0).getPoint2f();
Point2f n1 = el.getNode(1).getPoint2f();
Point2f n2 = el.getNode(2).getPoint2f();
path.moveTo(n0.x, n0.y);
path.lineTo(n1.x, n1.y);
path.lineTo(n2.x, n2.y);
path.closePath();
if(path.contains(p))
return el;
}
return null;
}
/**
* @return the number of elements in this list
*/
public int getElementCount(){
return elements.size();
}
/**
* @return a list of all elements in this mesh
*/
public List<TriElement> getElements() {
return elements;
}
/**
* @param index edge index
* @return the edge with the given index
*/
public Edge getEdge(int index){
return edges.get(index);
}
/**
* @return the number of edges in this mesh
*/
public int getEdgeCount(){
return edges.size();
}
/**
* @return a list of all edges in this mesh
*/
public List<Edge> getEdges() {
return edges;
}
/**
* Finalizes the construction of this mesh. This method is to be called
* after all nodes and elements have been added. It will do the following:
* - trim the node and element list to their current size to save memory
* - assign each node the elements it belongs to
* - ensure counterclockwise order of internal nodes. This will force outer
* edges to be oriented in counterclockwise direction along the perimeter.
* - generate edges
* - assign edges to elements, edges to nodes and elements to nodes
* - renumber all entities to ensure list indices concur with global indices
* - mark outer nodes and edges
*/
public void finalizeConstruction(){
if(finalized){
System.err.println("Mesh has already been finalized.");
return;
}
nodes.trimToSize();
elements.trimToSize();
assignElementsToNodes();
reorientElements();
generateEdges();
renumberAll();
computeOuterNodesAndEdges();
computeBounds();
finalized = true;
}
/**
* Assigns to each node all elements in wich it is contained.
*/
private void assignElementsToNodes(){
for(TriElement el : elements){
for(Node2D node : el.getNodes()){
node.addElement(el);
}
}
int count = 0;
for (int i = 0; i < nodes.size(); i++) {
if(nodes.get(i).getElementCount() == 0){
nodes.remove(i);
i--;
count++;
}
}
if(count > 0)
System.out.println(count + " unused nodes removed");
}
/**
* Generates a list of global edges and assigns each element its edges. This
* method will also assign edges to each node and each element to its edges.
*/
private void generateEdges(){
edges = new ArrayList<>(2 * elements.size());
for(TriElement el : elements){
int prev = 2;
for(int i=0; i<3; i++){
//constuct new edge
Node2D n1 = el.getNode(prev);
Node2D n2 = el.getNode(i);
Edge edge = new Edge(n1, n2);
// check for duplicate (if edge exists use existing edge)
int index = edges.indexOf(edge);
if(index == -1)
edges.add(edge);
else
edge = edges.get(index);
// assign edge to element and nodes
el.setEdge(i, edge);
n1.addEdge(edge);
n2.addEdge(edge);
// assign element to edge
edge.addElement(el);
prev = i;
}
}
edges.trimToSize();
}
/**
* renumbers all mesh entities according to their list index to ensure
* equality between the list index and globalIndex property.
*/
private void renumberAll(){
//renumber nodes
for(int i=0; i<nodes.size(); i++)
nodes.get(i).setGlobalIndex(i);
//renumber elements
for(int i=0; i<elements.size(); i++)
elements.get(i).setGlobalIndex(i);
//renumber edges
for(int i=0; i<edges.size(); i++)
edges.get(i).setGlobalIndex(i);
}
/**
* Calculates the external rectangular bounds for this mesh.
*/
private void computeBounds(){
Point2D p = nodes.get(0).getPoint();
bounds = new Rectangle2D.Double(p.getX(), p.getY(), 0, 0);
for (Node2D n : nodes) {
bounds.add(n.getPoint());
}
}
/**
* determines which nodes and edges are on the mesh boundary and marks them
* accordingly.
*/
private void computeOuterNodesAndEdges(){
HashSet<Edge> outerEdgeSet = new HashSet<>(getEdgeCount()/5);
// compute outer edges
for (Edge edge : edges) {
if(edge.getElementCount() == 1){
edge.setOuter(true);
outerEdgeSet.add(edge);
} else {
edge.setOuter(false);
}
}
outerEdges = new ArrayList<>(outerEdgeSet.size() + 5);
outerNodes = new ArrayList<>(outerEdgeSet.size() + 5);
// sort edges, fill outer edge and node list and mark outer nodes
Edge current, next, first;
next = first = null;
while(!outerEdgeSet.isEmpty()){
if(next == first){ // end of loop --> start new loop
current = outerEdgeSet.iterator().next();// get any element
first = current;
} else {
current = next;
}
if(current == null)
throw new NullPointerException("null edge in outer edge set");
// update lists and mark node
outerEdges.add(current);
outerNodes.add(current.getNode(0));
current.getNode(0).setOuter(true);
outerEdgeSet.remove(current);
//find next
next = null;
for (Edge edge : current.getNode(1).getEdges()) {
if(edge == current)
continue;
if(edge.isOuter()){
next = edge;
break;
}
}
if(next == null)
throw new RuntimeException("Error computing mesh outlines: open loop!");
if(next == first){ // loop closed
// add 'null' to mark loop end
outerEdges.add(null);
outerNodes.add(null);
}
}
}
private void reorientElements(){
for(TriElement el : elements){
Point2f v0 = el.getNode(0).getPoint2f();
Point2f v1 = el.getNode(1).getPoint2f();
Point2f v2 = el.getNode(2).getPoint2f();
v1.sub(v0); // from node 0 to 1
v2.sub(v0); // from node 0 to 2
float cross = v1.x*v2.y - v1.y*v2.x;
if(cross < 0){
el.reorient();
} else if(cross == 0){
System.err.println("Distorted element: " + el.globalIndex);
}
}
}
public TriMesh2D copy(){
TriMesh2D mesh = new TriMesh2D();
// copy nodes
for(Node2D n : nodes)
mesh.addNode(n.copy());
// add new element for each existing element
for(TriElement el : elements){
int[] nodeIdx = new int[3];
for(int i=0; i<3; i++)
nodeIdx[i] = el.getNode(i).getIndex();
mesh.addNewElement(el.getIndex(), nodeIdx);
}
mesh.finalizeConstruction();
return mesh;
}
/**
* @return a list of the outer nodes of this mesh sorted in
* counter-clockwise sequence. Loops are separated by null values in the
* list.
*/
public List<Node2D> getOuterNodes() {
return outerNodes;
}
/**
* @return a list of the outer edges of this mesh sorted in
* counter-clockwise sequence. Loops are separated by null values in the
* list.
*/
public List<Edge> getOuterEdges() {
return outerEdges;
}
public GeneralPath createOutlinePath() {
GeneralPath p = new GeneralPath();
p.moveTo(0f, 0f);
Point2f v;
boolean first = true;
for (Node2D node : getOuterNodes()) {
if(node == null){
p.closePath();
first = true;
} else if(first){
v = node.getPoint2f();
p.moveTo(v.x, v.y);
first = false;
} else {
v = node.getPoint2f();
p.lineTo(v.x, v.y);
}
}
return p;
}
public GeneralPath createWireframePath(){
GeneralPath wireframe = new GeneralPath();
Point2f start, end;
for (Edge e : getEdges()) {
// skip border edges
if (e.isOuter()) {
continue;
}
// inner edge --> add to wireframe
start = e.getNode(0).getPoint2f();
end = e.getNode(1).getPoint2f();
wireframe.moveTo(start.x, start.y);
wireframe.lineTo(end.x, end.y);
}
return wireframe;
}
@Override
public String toString() {
return String.format("Triangular 2D-mesh [%d nodes; %d elements]", getNodeCount(), getElementCount());
}
/**
* @return the bounds
*/
public Rectangle2D getBounds() {
return bounds;
}
}