@Override public ContourAggregates<N> process(Aggregates<? extends N> aggregates, Renderer rend) { Single<N>[] ts = LocalUtils.stepTransfers(contourLevels, fill); Transfer.Specialized<N, N> t = new Fan.Specialized<>(new MergeContours<N>(aggregates.defaultValue()), ts, aggregates); return (ContourAggregates<N>) rend.transfer(aggregates, t); }
@Override public MC_TYPE at(int x, int y, Aggregates<? extends Boolean> aggregates) { int code = 0; if (aggregates.get(x - 1, y - 1)) { code = code | DOWN_INDEX_LEFT; } if (aggregates.get(x, y - 1)) { code = code | DOWN_INDEX_RIGHT; } if (aggregates.get(x - 1, y)) { code = code | UP_INDEX_LEFT; } if (aggregates.get(x, y)) { code = code | UP_INDEX_RIGHT; } return MC_TYPE.get(code); }
@Override public Boolean at(int x, int y, Aggregates<? extends N> aggregates) { Number v = aggregates.get(x, y); if (v == null) { return false; } double delta = threshold.doubleValue() - v.doubleValue(); return delta < 0; }
@Override public ContourAggregates<N> process(Aggregates<? extends N> aggregates, Renderer rend) { Aggregates<? extends N> padAggs = new PadAggregates<>(aggregates, null); Aggregates<Boolean> isoDivided = rend.transfer(padAggs, new ISOBelow<>(threshold)); Aggregates<MC_TYPE> classified = rend.transfer(isoDivided, new MCClassifier()); Shape s = Assembler.assembleContours(classified, isoDivided); GlyphList<Shape, N> contours = new GlyphList<>(); contours.add(new SimpleGlyph<>(s, threshold)); if (!fill) { isoDivided = rend.transfer(isoDivided, new General.Simplify<>(isoDivided.defaultValue())); } Aggregates<N> base = rend.transfer( isoDivided, new General.MapWrapper<>(true, threshold, aggregates.defaultValue())); return new ContourAggregates<>(base, contours); }
/** * Build a single path from all of the contour parts. * * <p>May be disjoint and have holes (thus GeneralPath). * * @param classified The line-segment classifiers * @param isoDivided Original classification, used to disambiguate saddle conditions * @return Resulting contour */ public static final GeneralPath assembleContours( Aggregates<MC_TYPE> classified, Aggregates<Boolean> isoDivided) { GeneralPath isoPath = new GeneralPath(GeneralPath.WIND_EVEN_ODD); // Find an unambiguous case of an actual line, follow it around and build the line. // Stitching sets the line segments that have been "consumed" to MC_TYPE.empty, so segments // are only processed once. for (int x = classified.lowX(); x < classified.highX(); x++) { for (int y = classified.lowY(); y < classified.highY(); y++) { MC_TYPE type = classified.get(x, y); if (type != MC_TYPE.empty && type != MC_TYPE.surround && type != MC_TYPE.diag_one && type != MC_TYPE.diag_two) { stichContour(classified, isoDivided, isoPath, x, y); } } } return isoPath; }
/** * An iso level can be made of multiple regions with holes in them. This builds one path (into * the passed GeneralPath) that represents one connected contour. * * @param isoData Marching-cubes classification at each cell * @param isoDivided The boolean above/below classification for each cell (to disambiguate * saddles) * @param iso The path to build into */ public static void stichContour( Aggregates<MC_TYPE> isoData, Aggregates<Boolean> isoDivided, GeneralPath iso, int startX, int startY) { int x = startX, y = startY; SIDE prevSide = SIDE.NONE; // Found an unambiguous iso line at [r][c], so start there. MC_TYPE startCell = isoData.get(x, y); Point2D nextPoint = startCell.firstSide(prevSide).nextPoint(x, y); iso.moveTo(nextPoint.getX(), nextPoint.getY()); prevSide = isoData.get(x, y).secondSide(prevSide, isoDivided.get(x, y)); // System.out.printf("-------------------\n); do { // Process current cell MC_TYPE curCell = isoData.get(x, y); nextPoint = curCell.secondSide(prevSide, isoDivided.get(x, y)).nextPoint(x, y); // System.out.printf("%d,%d: %s\n",x,y,curCell.secondSide(prevSide, isoDivided.get(x,y))); iso.lineTo(nextPoint.getX(), nextPoint.getY()); SIDE nextSide = curCell.secondSide(prevSide, isoDivided.get(x, y)); isoData.set(x, y, curCell.clearWith()); // Erase this marching cube line entry // Advance for next cell prevSide = nextSide; switch (nextSide) { case LEFT: x -= 1; break; case RIGHT: x += 1; break; case BOTTOM: y += 1; break; case TOP: y -= 1; break; case NONE: throw new IllegalArgumentException( "Encountered side NONE after starting contour line."); } } while (x != startX || y != startY); iso.closePath(); }
@Override public int highY() { return base.highY(); }
@Override public int lowY() { return base.lowY(); }
@Override public A defaultValue() { return base.defaultValue(); }
@Override public void set(int x, int y, A val) { base.set(x, y, val); }
@Override public A get(int x, int y) { return base.get(x, y); }
@Override public Iterator<A> iterator() { return base.iterator(); }
public int highY() { return base.highY() + 1; }
public int lowY() { return base.lowY() - 1; }
public A defaultValue() { return base.defaultValue(); }
@Override public A get(int x, int y) { if ((x >= base.lowX() && x < base.highX()) && (y >= base.lowY() && y < base.highY())) { return base.get(x, y); // Its inside } else if ((x == base.lowX() - 1 || x == base.highX() + 1) && (y >= base.lowY() - 1 && y < base.highY() + 1)) { return pad; // Its immediate above or below } else if ((y == base.lowY() - 1 || y == base.highY() + 1) && (x >= base.lowX() - 1 && x < base.highX() + 1)) { return pad; // Its immediately left or right } else { return base.defaultValue(); } }