All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.gsi.chart.renderer.spi.marchingsquares.PathGenerator Maven / Gradle / Ivy

package de.gsi.chart.renderer.spi.marchingsquares;

import static javafx.geometry.Side.BOTTOM;
import static javafx.geometry.Side.LEFT;
import static javafx.geometry.Side.RIGHT;
import static javafx.geometry.Side.TOP;

import javafx.geometry.Side;

/**
 * 

* An object that knows how to translate a Grid of Marching Squares Contour * Cells into a Java AWT General Path. *

*/ public class PathGenerator { private static final double EPSILON = 1E-7; PathGenerator() { super(); } /** *

* Construct a GeneralPath representing the isoline, itself represented * by a given Grid. *

*

* IMPLEMENTATION NOTE: This method is destructive. It alters * the Grid instance as it generates the resulting path. If the 'original' * Grid instance is needed after invoking this method then it's the * responsibility of the caller to deep clone it before passing it here. *

* * @param grid the matrix of contour cells w/ side crossing coordinates * already interpolated and normalized; i.e. in the range 0.0..1.0. * @return the geometries of a contour, including sub-path(s) for disjoint * area final holes. */ public GeneralPath generalPath(final Grid grid) { final GeneralPath result = new GeneralPath(); for (int r = 0; r < grid.rowCount; r++) { for (int c = 0; c < grid.colCount; c++) { // find a start node... final Cell cell = grid.getCellAt(r, c); if (cell != null && !cell.isTrivial() && !cell.isSaddle()) { // complete the [sub-]path and close it update(grid, r, c, result); } } } return result; } /** *

* Return the first side that should be used in a CCW traversal. *

* * @param cell the Cell to process. * @param prev previous side, only used for saddle cells. * @return the 1st side of the line segment of the designated cell. */ private static Side firstSide(final Cell cell, final Side prev) { switch (cell.getCellNdx()) { case 1: case 3: case 7: return LEFT; case 2: case 6: case 14: return BOTTOM; case 4: case 11: case 12: case 13: return RIGHT; case 8: case 9: return TOP; case 5: switch (prev) { case LEFT: return RIGHT; case RIGHT: return LEFT; default: throw new NoSaddlePointException(cell, prev); } case 10: switch (prev) { case BOTTOM: return TOP; case TOP: return BOTTOM; default: throw new NoSaddlePointException(cell, prev); } default: final String m = "Attempt to use a trivial cell as a start node: " + cell; throw new IllegalStateException(m); } } protected static class NoSaddlePointException extends IllegalStateException { private static final long serialVersionUID = -5628254997299110176L; NoSaddlePointException(final Cell cell, final Side prev) { super("Saddle w/ no connected neighbour; Cell = " + cell + ", previous side = " + prev); } } /** *

* Find the side on which lies the next cell to use in a CCW traversal. *

* * @param cell the Cell to process. * @param prev previous side, only used for saddle cells. * @return side where the next cell is to be picked. */ private Side nextSide(final Cell cell, final Side prev) { return secondSide(cell, prev); } /** *

* Return the second side that should be used in a CCW traversal. *

* * @param cell the Cell to process. * @param prev previous side, only used for saddle cells. * @return the 2nd side of the line segment of the designated cell. */ private Side secondSide(final Cell cell, final Side prev) { switch (cell.getCellNdx()) { case 8: case 12: case 14: return LEFT; case 1: case 9: case 13: return BOTTOM; case 2: case 3: case 11: return RIGHT; case 4: case 6: case 7: return TOP; case 5: switch (prev) { case LEFT: return cell.isFlipped() ? BOTTOM : TOP; case RIGHT: return cell.isFlipped() ? TOP : BOTTOM; default: final String m = "Saddle w/ no connected neighbour; Cell = " + cell + ", previous side = " + prev; throw new IllegalStateException(m); } case 10: switch (prev) { case BOTTOM: return cell.isFlipped() ? RIGHT : LEFT; case TOP: return cell.isFlipped() ? LEFT : RIGHT; default: final String m = "Saddle w/ no connected neighbour; Cell = " + cell + ", previous side = " + prev; throw new IllegalStateException(m); } default: final String m = "Attempt to use a trivial Cell as a node: " + cell; throw new IllegalStateException(m); } } /** *

* A given contour can be made up of multiple disconnected regions, each * potentially having multiple holes. Both regions and holes are captured as * individual sub-paths. *

*

* The process is iterative. It starts w/ an empty GeneralPath instance * and continues until all Cells are processed. With every invocation the * GeneralPath object is updated to reflect the new sub-path(s). *

*

* Once a non-saddle cell is used it is cleared so as to ensure it will * not be re-used when finding sub-paths w/in the ofinal l path. *

* final * @param grid on input the matrix of cells representinfinal ven contour. * Note that the process will alter the Cells, so on output the original * Grid instance _will_ be modified. In other words this method is NOT * idempotent when using the same object references and values. * * @param grid the grid * @param r row index of the start Cell. * @param c column index of the start Cell. * @param path a non-null GeneralPath instance to update. */ private void update(final Grid grid, int r, int c, final GeneralPath path) { Side prevSide = null; // was: NONE final Cell start = grid.getCellAt(r, c); float[] pt = start.getXY(PathGenerator.firstSide(start, prevSide)); float x = c + pt[0]; // may throw NPE float y = r + pt[1]; // likewise path.moveTo(x, y); // prepare for a new sub-path pt = start.getXY(secondSide(start, prevSide)); float xPrev = c + pt[0]; float yPrev = r + pt[1]; prevSide = nextSide(start, prevSide); switch (prevSide) { case BOTTOM: r--; break; case LEFT: c--; break; case RIGHT: c++; break; case TOP: r++; // fall through break; default: break; } start.clear(); Cell currentCell = grid.getCellAt(r, c); while (!start.equals(currentCell)) { // we want object reference equality pt = currentCell.getXY(secondSide(currentCell, prevSide)); x = c + pt[0]; y = r + pt[1]; if (Math.abs(x - xPrev) > PathGenerator.EPSILON && Math.abs(y - yPrev) > PathGenerator.EPSILON) { path.lineTo(x, y); } xPrev = x; yPrev = y; prevSide = nextSide(currentCell, prevSide); switch (prevSide) { case BOTTOM: r--; break; case LEFT: c--; break; case RIGHT: c++; break; case TOP: r++; break; default: // System.out.println( // "update: Potential loop! Current cell = " + currentCell + ", previous side = " + prevSide); break; } currentCell.clear(); currentCell = grid.getCellAt(r, c); } path.closePath(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy