package bs.bs2d.gui.plot;
import bs.bs2d.fea.Edge;
import bs.bs2d.fea.Node2D;
import bs.bs2d.fea.TriElement;
import bs.bs2d.fea.TriMesh2D;
import bs.bs2d.geom.JTSUtils;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.MultiPolygon;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.geom.GeneralPath;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.vecmath.Point2f;
/**
* Provides utility methods for creating stress plots. The methods providied by
* this class are used by the StressPlot class internally. To create a stress
* plot use the static factory methods of the StressPlot class.
* @author Djen
*/
public abstract class ScalarPlotUtils {
/**
* Generates thresholds for a linear scale scalar plot. Used by ScalarPlot
* internally. To create scalar plots use the static factory methods of the
* ScalarPlot class.
* @param scalar the scalar
* @param res the plot resolution
* @return linearly scaled thrsholds for the given scalar field
*/
public static float[] getLinScaleThresholds(float[] scalar, int res){
// determine min and max
float min, max;
min = max = scalar[0];
for(float d : scalar){
if(d < min)
min = d;
if(d > max)
max = d;
}
// split range in equal sections
float[] thrsh = new float[res+1];
float step = (max-min)/(res);
for (int i = 0; i < res; i++) {
thrsh[i] = min + i*step;
}
thrsh[res] = max; // avoid rounding error
return thrsh;
}
/**
* Generates thresholds for a uniform area scalar plot. Used by ScalarPlot
* internally. To create scalar plots use the static factory methods of the
* ScalarPlot class.
* @param scalar the scalar
* @param res the plot resolution
* @return uniform area thrsholds for the given scalar field
*/
public static float[] getUniAreaThresholds(float[] scalar, int res){
// get sorted stress values
float[] sstress = Arrays.copyOf(scalar, scalar.length);
Arrays.sort(sstress);
// divide into groups of equal size
float[] thrsh = new float[res+1];
float step = (float)sstress.length/res;
for(int i=0; i < res; i++){
thrsh[i] = sstress[(int)(i*step)];
}
thrsh[res] = sstress[sstress.length-1];
return thrsh;
}
/**
* Computes the relative position along the given edge where the scalar
* value linearly interpolated between the two nodes crosses the threshold
* @param e the edge
* @param scalar the list of scalars to retrieve the significant values in
* the two nodes (identified by global index)
* @param thrsh the threshold
* @return the relative position of the threshold value on the edge
*/
private static float getRelPosOnEdge(Edge e, float[] scalar, float thrsh){
float sstart = scalar[e.getNode(0).getIndex()];
float send = scalar[e.getNode(1).getIndex()];
if((sstart >= thrsh && send >= thrsh) || (sstart < thrsh && send < thrsh))
System.err.println("invalid edge");
if(sstart == send)
return 0;
return Math.abs((thrsh-sstart)/(send-sstart));
}
/**
* Computes the coordinates of the point at 'pos' between the start and
* end point of the given edge.
* @param edge the Edge
* @param pos relative position on the edge (0 <= pos <= 1)
* @return the Point at relative position 'pos' on the given edge
*/
private static Point2f getPointOnEdge(Edge edge, float pos){
// start point vector
Point2f s = edge.getNode(0).getPoint2f();
// end point vector
Point2f e = edge.getNode(1).getPoint2f();
s.interpolate(e, pos);
return s;
}
/**
* Computes the relative point along the given edge where the scalar
* value linearly interpolated between the two nodes crosses the threshold
* @param e the edge
* @param scalar the list of scalars to retrieve the significant values in
* the two nodes (identified by global index)
* @param thrsh the threshold
* @return the relative point of the threshold on the edge
*/
private static Point2f getRelPointOnEdge(Edge e, float[] scalar, float thrsh){
return getPointOnEdge(e, getRelPosOnEdge(e, scalar, thrsh));
}
/**
* Copmutes the area(s) of the given mesh in wich the specified scalar is
* above the given threshold.
* @param m the Mesh
* @param scalar a collection of the scalar values corresponding to the
* nodes of the given mesh (with 1-based node indices).
* @param thrsh the threshold for this area plot
* @return a GeneralPath describing all the areas of the given mesh where
* the given scalar value is above the threshold.
*/
private static Shape createScalarPlotArea2(TriMesh2D m, float[] scalar, float thrsh) {
// collect the outline segments element-wise
LinkedList<Point2f[]> lines = new LinkedList<>();
for (TriElement element : m.getElements()) {
Point2f[] line = new Point2f[2];
int i = 0;
for (Edge edge : element.getEdges()) {
int start = edge.getNode(0).getIndex();
int end = edge.getNode(1).getIndex();
float min = Math.min(scalar[start], scalar[end]);
float max = Math.max(scalar[start], scalar[end]);
if (max >= thrsh) {
if (min < thrsh) {// edge crosses area outlines
line[i] = getRelPointOnEdge(edge, scalar, thrsh);
if (edge.isOuter()) {// add line segments along outer edge
//find node above threshold
Node2D n = edge.getNode(0);
if (scalar[edge.getNode(1).getIndex()] >= thrsh) {
n = edge.getNode(1);
}
// add line from intersection to node
lines.add(new Point2f[]{line[i], n.getPoint2f()});
}
i++;
} else if(edge.isOuter()){ // min is also >= thrsh
lines.add(edge.getNodesAsPoints());
}
}
}
if(i == 2){
lines.add(line);
}
}
// remove invalid segments
for (int i = 0; i < lines.size(); i++) {
// check for zero length
Point2f[] line = lines.get(i);
if(line[0].equals(line[1])){
lines.remove(i--);
continue;
}
// check for duplicates
for (int j = i+1; j < lines.size(); j++) {
if(Arrays.equals(lines.get(i), lines.get(j))){
lines.remove(j);
}
}
}
// sort segments and construct path
GeneralPath p = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
if(lines.isEmpty())
return p;
Point2f[] points = lines.getFirst();
Point2f point;
Point2f firstPoint = points[0];
Point2f prevPoint = points[1];
p.moveTo(firstPoint.x, firstPoint.y);
p.lineTo(prevPoint.x, prevPoint.y);
lines.remove(points);
while(true){
point = null;
for(int i=0; i<lines.size(); i++){
points = lines.get(i);
if(points[0].equals(prevPoint)){
point = points[1];
break;
}
if (points[1].equals(prevPoint)){
point = points[0];
break;
}
}
if(point == null){
System.out.println("zero length line: " + points[0].equals(points[1]));
// throw new NullPointerException("Error constructing scalar plot: area outlines not closed");
System.err.println("Error constructing scalar plot: area outlines not closed");
Toolkit.getDefaultToolkit().beep();
return null;
}
if(firstPoint.equals(point)){
// close loop
p.closePath();
lines.remove(points);
if(lines.isEmpty())
break;
// start new loop
points = lines.getFirst();
firstPoint = points[0];
p.moveTo(firstPoint.x, firstPoint.y);
point = points[1];
}
p.lineTo(point.x, point.y);
lines.remove(points);
prevPoint = point;
}
return p;
}
private static Shape createScalarPlotArea(TriMesh2D m, float[] scalar, float thrsh){
List<Edge> edges = m.getEdges();
HashMap<Edge, Point2f> intPoints = new HashMap<>(edges.size()/3);
for (Edge edge : edges) {
Node2D[] n = edge.getNodes();
float f1 = scalar[n[0].getIndex()];
float f2 = scalar[n[1].getIndex()];
if ((f1 >= thrsh && f2 < thrsh) || (f2 >= thrsh && f1 < thrsh)) {
Point2f p1 = n[0].getPoint2f();
Point2f p2 = n[1].getPoint2f();
intPoints.put(edge, interpolate(p1, f1, p2, f2, thrsh));
}
}
List<TriElement> elements = m.getElements();
MultiPolygon[] polys = new MultiPolygon[elements.size()];
for (int i = 0; i < elements.size(); i++) {
TriElement element = elements.get(i);
Shape s = getElementSubShape(element, scalar, thrsh, intPoints);
if(s != null){
polys[i] = JTSUtils.createMultiPolygon(s);
}
}
GeometryCollection gc;
gc = JTSUtils.getFactory().createGeometryCollection(polys);
Geometry union = gc.buffer(0);
return JTSUtils.toShape(union);
}
/**
* Computes the region of element e that is above the threshold thrsh.
* @param e a tri-element
* @param scalar the scalar field
* @param thrsh the scalar threshold
* @return the subshape of e that is above the threshold in the scalar
* field. If the whole element is above the threshold the result will be
* kongurent to e.getShape(). If the whole element is below the threshold
* an empty shape is returned.
*/
private static Shape getElementSubShape(TriElement e, float[] scalar, float thrsh, Map<Edge, Point2f> intPoints){
GeneralPath path = new GeneralPath();
// find first node above threshold
Point2f p = null;
Node2D n = null;
int i;
for (i = 0; i < 3; i++) {
n = e.getNode(i);
if(scalar[n.getIndex()] >= thrsh){
p = n.getPoint2f();
break;
}
}
// no node found --> return empty path
if(p == null)
return path;
path.moveTo(p.x, p.y);
boolean b, prevB = true; // was previous node above thrsh?
Node2D prevN = n;
int end = i + 3;
for (i++; i <= end; i++) {
n = e.getNode(i % 3);
p = n.getPoint2f();
b = scalar[n.getIndex()] >= thrsh;
if(b != prevB){ // edge crosses threshold -> interpolate point
Edge edge = new Edge(prevN, n);
Point2f pInt = intPoints.get(edge);
path.lineTo(pInt.x, pInt.y);
}
if(b){
path.lineTo(p.x, p.y);
}
prevB = b;
prevN = n;
}
return path;
}
private static Point2f interpolate(Point2f p1, float f1, Point2f p2, float f2, float thrsh){
if(f1 == f2) // avoid division by 0;
return p2;
f2 -= f1;
thrsh -= f1;
thrsh /= f2;
if(thrsh < 0 || thrsh > 1)
System.err.println("Interpolartion factor is out of range [0 1].");
p1.interpolate(p2, thrsh);
return p1;
}
public static Shape[] createScalarPlotAreas(TriMesh2D m, float[] scalar, float[] thresholds){
Shape[] plot = new Shape[thresholds.length - 1];
plot[0] = m.createOutlinePath(); // first area includes the whole mesh
for(int i=1; i<plot.length; i++){
plot[i] = createScalarPlotArea(m, scalar, thresholds[i]);
}
return plot;
}
}