0

I'm currently working on a rewrite for a Path drawing algorythm. I'm using apache-commons-math's Spline Interpolator for getting a smooth path through all given points in 2D Space...

Currently I have:

/**
 * Draws a route on a map.
 */
public class MapRouteDrawer {

  private static final SplineInterpolator splineInterpolator = new SplineInterpolator();

  /**
   * Draws the route to the screen, does nothing if null.
   */
  public static void drawRoute(final Graphics2D graphics, final RouteDescription routeDescription, final MapPanel view, final MapData mapData, final String movementLeftForCurrentUnits) {
    if (routeDescription == null) {
      return;
    }
    final Route route = routeDescription.getRoute();
    if (route == null) {
      return;
    }

    final Point[] points = getRoutePoints(routeDescription, mapData);
    final int xOffset = view.getXOffset();
    final int yOffset = view.getYOffset();
    final int jointsize = 10;
    final int numTerritories = route.getAllTerritories().size();
    //set thickness and color of the future drawings
    graphics.setStroke(new BasicStroke(3.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
    graphics.setPaint(Color.red);
    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    if(Arrays.asList(points).contains(null)){//If the Array is null at some point
      return;
    }

    if(numTerritories <= 1 || points.length <= 2){
      drawLineWithTranslate(graphics, new Line2D.Float(routeDescription.getStart(), routeDescription.getEnd()), xOffset, yOffset);
      graphics.fillOval((routeDescription.getEnd().x - xOffset) - jointsize / 2, (routeDescription.getEnd().y - yOffset) - jointsize / 2, jointsize, jointsize);
    }
    else{
      drawCurvedPath(graphics, points, view);
    }
  }

  }

  private static double[] getIndex(Point[] points) {
    final double[] index = new double[points.length];
    for(int i = 0; i < points.length; i++){
      index[i] = i;
    }
    return index;
  }

  private static void drawLineWithTranslate(Graphics2D graphics, Line2D line2D, double translateX, double translateY) {
      final Line2D line = (Line2D) line2D;
      final Point2D point1 = new Point2D.Double(line.getP1().getX() - translateX, line.getP1().getY() - translateY);
      final Point2D point2 = new Point2D.Double(line.getP2().getX() - translateX, line.getP2().getY() - translateY);
      graphics.draw(new Line2D.Double(point1, point2));
  }

  private static Point[] getRoutePoints(RouteDescription routeDescription, MapData mapData){
    final List<Territory> territories = routeDescription.getRoute().getAllTerritories();
    final int numTerritories = territories.size();
    final Point[] points = new Point[numTerritories];
    for (int i = 0; i < numTerritories; i++) {
      points[i] = mapData.getCenter(territories.get(i));
    }
    if (routeDescription.getStart() != null) {
      points[0] = routeDescription.getStart();
    }
    if (routeDescription.getEnd() != null && numTerritories > 1) {
      points[numTerritories - 1] = new Point(routeDescription.getEnd());
    }
    return points;
  }

  private static double[] pointsXToDoubleArray(Point[] points){
    double[] result = new double[points.length];
    for(int i = 0; i < points.length; i++){
      result[i] = points[i].getX();
    }
    return result;
  }
  private static double[] pointsYToDoubleArray(Point[] points){
    double[] result = new double[points.length];
    for(int i = 0; i < points.length; i++){
      result[i] = points[i].getY();
    }
    return result;
  }

  private static double[] getCoords(PolynomialSplineFunction curve, float stepSize){
    final double[] coords = new double[(int) (curve.getN() / stepSize)];
    for(int i = 0; i < curve.getN() / stepSize; i++){
      coords[i] = curve.value(i * stepSize);
    }
    return coords;
  }

  private static void drawCurvedPath(Graphics2D graphics, Point[] points, MapPanel view){
    final double[] index = getIndex(points);
    final float stepSize = 0.01f;//TODO calculating a step size that makes sense
    final PolynomialSplineFunction xcurve = splineInterpolator.interpolate(index, pointsXToDoubleArray(points));
    final PolynomialSplineFunction ycurve = splineInterpolator.interpolate(index, pointsYToDoubleArray(points));
    final double[] xcoords = getCoords(xcurve, stepSize);
    final double[] ycoords = getCoords(ycurve, stepSize);

    for(int i = 1; i < xcoords.length; i++){
      //TODO maybe a line is not the best way to draw this...
      drawLineWithTranslate(graphics, new Line2D.Double(xcoords[i-1], ycoords[i-1], xcoords[i], ycoords[i]), view.getXOffset(), view.getYOffset());
    }
  }
}

The Idea behind this is, that since the SplineInterpolator does only accept functions (e.g. f(x) = y) and x has to be increasing we split the point array into 2 double arrays and interpolate those 2 times...
First the X Values, then the Y values...
As X values a "dummy array" called the "index" is taken with the first value being 0 the second 1 the third 2 and so on...
In order to draw this path I'm drawing a line from Point 0 to 1, 1 to 2, 2 to 3 and so on...

There are 2 Things to consider...

  1. Does it make sense choosing 1 as step size in the index? This could lead to inaccuracies since a java double has only 64 bits and we are streching and squeezing the values with our static 1 step size. And if so, what could I change in order to optimize this...
  2. And second, how can those 2 double arrays be draw a better way? my attempt to draw multiple lines does not look very good - is this because I'm reading the interpolated values in too big steps?

Any help is very appreciated

EDIT: Path Zoomed in Whole Image

RoiEX
  • 1,186
  • 1
  • 10
  • 37

1 Answers1

1

Choosing 1 as step size for the 'index' array is the so-called uniform parametrization, which typically will not lead to a good result unless your data points also distribute relatively uniformly. I would recommend using either chord-length parametrization or centripetal parametrization as in the following:

t0 = 0.0
t1 = d1/L
t2 = t1 + d2/L  
t3 = t2 + d3/L
............
t(n-1)= 1.0.

where

d1=|P1-P0|^e, d2=|P2-P1|^e, d3=|P3-P2|^e and L = d1+d2+d3+.....d(n-1). 

For chord-length parametrization, use e=1.0 in above formula. For centripetal parametrization, use e=0.5. Note that using e=0.0 will simply result in the uniform parametrization. If your data point has very non-uniform distribution (i.e., some distances between points are huge and some are small), centripetal parametrization will often lead to better result than chord-length parametrization.

fang
  • 3,473
  • 1
  • 13
  • 19
  • I did use ~0.1 steps or so in order to draw, but still the path seemed edgy even it interpolated as expected – RoiEX May 22 '16 at 04:46
  • From your codes, you sample 'curve.getN() / stepSize' points on a curve when drawing it. What is the actual number for 'curve.getN() / stepSize'? – fang May 22 '16 at 04:51
  • This is the number indicating how often the drawing method should be looped, shop that every stepSize step heus drawn – RoiEX May 22 '16 at 04:53
  • If possible, add a picture to show the path you drew so that we can understand better why 'it does not look very good'. – fang May 22 '16 at 04:58
  • I will if I'm back on my PC - today – RoiEX May 22 '16 at 04:58
  • I added some Screenshots... btw. you can find the complete code with repository [here](https://github.com/RoiEXLab/triplea/blob/RouteDrawer-2/src/games/strategy/triplea/ui/MapRouteDrawer.java) – RoiEX May 23 '16 at 08:57
  • The path shown in the picture looks fine to me. Also, have you try either the chord-length or centripetal parametrization? – fang May 23 '16 at 19:27