Ignore:
Timestamp:
2016-10-18T13:04:35+02:00 (9 years ago)
Author:
michael2402
Message:

Fix #13793: Clip paths in a way that let's the dashes stay in place.

Location:
trunk/src/org/openstreetmap/josm/gui
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/MapViewState.java

    r11141 r11144  
    553553         * @since 10874
    554554         */
    555         public MapViewPoint interpolate(MapViewPoint p1, int i) {
     555        public MapViewPoint interpolate(MapViewPoint p1, double i) {
    556556            return new MapViewViewPoint((1 - i) * getInViewX() + i * p1.getInViewX(), (1 - i) * getInViewY() + i * p1.getInViewY());
    557557        }
  • trunk/src/org/openstreetmap/josm/gui/draw/MapViewPath.java

    r11026 r11144  
    22package org.openstreetmap.josm.gui.draw;
    33
    4 import java.awt.geom.Point2D;
    5 import java.text.MessageFormat;
     4import java.awt.BasicStroke;
     5import java.awt.Shape;
     6import java.awt.Stroke;
     7import java.awt.geom.PathIterator;
    68
    79import org.openstreetmap.josm.data.coor.EastNorth;
     
    1214import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
    1315
     16
    1417/**
    1518 * This is a version of a java Path2D that allows you to add points to it by simply giving their east/north, lat/lon or node coordinates.
    1619 * <p>
    17  * Paths far outside the view area are automatically clipped to increase performance.
     20 * It is possible to clip the part of the path that is outside the view. This is useful when drawing dashed lines. Those lines use up a lot of
     21 * performance if the zoom level is high and the part outside the view is long. See {@link #computeClippedLine(Stroke)}.
    1822 * @author Michael Zangl
    1923 * @since 10875
     
    9195    @Override
    9296    public MapViewPath lineTo(MapViewPoint p) {
    93         Point2D currentPoint = getCurrentPoint();
    94         if (currentPoint == null) {
    95             throw new IllegalStateException("Path not started yet.");
    96         }
    97         MapViewPoint current = state.getForView(currentPoint.getX(), currentPoint.getY());
    98         MapViewPoint end = p;
    99 
    100         MapViewRectangle clip = state.getViewClipRectangle();
    101         MapViewPoint entry = clip.getLineEntry(current, end);
    102         if (entry == null) {
    103             // skip this one - outside the view.
    104             super.moveTo(end);
    105         } else {
    106             if (!entry.equals(current)) {
    107                 super.moveTo(entry);
    108             }
    109             MapViewPoint exit = clip.getLineEntry(end, current);
    110             if (exit == null) {
    111                 throw new AssertionError(MessageFormat.format(
    112                         "getLineEntry produces wrong results when doing the reverse lookup. Attempt: {0}->{1}",
    113                         end.getEastNorth(), current.getEastNorth()));
    114             }
    115             super.lineTo(exit);
    116             if (!exit.equals(end)) {
    117                 super.moveTo(end);
    118             }
    119         }
     97        super.lineTo(p);
    12098        return this;
    12199    }
     
    191169        }
    192170    }
     171
     172    /**
     173     * Compute a line that is similar to the current path expect for that parts outside the screen are skipped using moveTo commands.
     174     *
     175     * The line is computed in a way that dashes stay in their place when moving the view.
     176     *
     177     * The resulting line is not intended to fill areas.
     178     * @param stroke The stroke to compute the line for.
     179     * @return The new line shape.
     180     */
     181    public Shape computeClippedLine(Stroke stroke) {
     182        if (stroke instanceof BasicStroke && ((BasicStroke) stroke).getDashArray() != null) {
     183            float length = 0;
     184            for (float f : ((BasicStroke) stroke).getDashArray()) {
     185                length += f;
     186            }
     187            return computeClippedLine(((BasicStroke) stroke).getDashPhase(), length);
     188        } else {
     189            return computeClippedLine(0, 0);
     190        }
     191    }
     192
     193    private Shape computeClippedLine(double strokeOffset, double strokeLength) {
     194        ClampingPathVisitor path = new ClampingPathVisitor(state.getViewClipRectangle(), strokeOffset, strokeLength);
     195        if (path.visit(getPathIterator(null))) {
     196            return path;
     197        } else {
     198            // could not clip the path.
     199            return this;
     200        }
     201    }
     202
     203    private class ClampingPathVisitor extends MapPath2D {
     204        private final MapViewRectangle clip;
     205        private double strokeOffset;
     206        private final double strokeLength;
     207        private MapViewPoint lastMoveTo;
     208
     209        private MapViewPoint cursor;
     210        private boolean cursorIsActive = false;
     211
     212        ClampingPathVisitor(MapViewRectangle clip, double strokeOffset, double strokeLength) {
     213            this.clip = clip;
     214            this.strokeOffset = strokeOffset;
     215            this.strokeLength = strokeLength;
     216        }
     217
     218        /**
     219         * Append a path to this one. The path is clipped to the current view.
     220         * @param it The iterator
     221         * @return true if adding the path was successful.
     222         */
     223        public boolean visit(PathIterator it) {
     224            double[] coords = new double[8];
     225            while (!it.isDone()) {
     226                int type = it.currentSegment(coords);
     227                switch (type) {
     228                case PathIterator.SEG_CLOSE:
     229                    visitClose();
     230                    break;
     231                case PathIterator.SEG_LINETO:
     232                    visitLineTo(coords[0], coords[1]);
     233                    break;
     234                case PathIterator.SEG_MOVETO:
     235                    visitMoveTo(coords[0], coords[1]);
     236                    break;
     237                default:
     238                    // cannot handle this shape - this should be very rare. We let Java2D do the clipping.
     239                    return false;
     240                }
     241                it.next();
     242            }
     243            return true;
     244        }
     245
     246        void visitClose() {
     247            drawLineTo(lastMoveTo);
     248        }
     249
     250        void visitMoveTo(double x, double y) {
     251            MapViewPoint point = state.getForView(x, y);
     252            lastMoveTo = point;
     253            cursor = point;
     254        }
     255
     256        void visitLineTo(double x, double y) {
     257            drawLineTo(state.getForView(x, y));
     258        }
     259
     260        private void drawLineTo(MapViewPoint next) {
     261            MapViewPoint entry = clip.getLineEntry(cursor, next);
     262            if (entry != null) {
     263                MapViewPoint exit = clip.getLineEntry(next, cursor);
     264                if (!cursorIsActive || !entry.equals(cursor)) {
     265                    entry = alignStrokeOffset(entry, cursor);
     266                    moveTo(entry);
     267                }
     268                lineTo(exit);
     269                cursorIsActive = exit.equals(next);
     270            }
     271            strokeOffset += cursor.distanceToInView(next);
     272
     273            cursor = next;
     274        }
     275
     276        private MapViewPoint alignStrokeOffset(MapViewPoint entry, MapViewPoint originalStart) {
     277            double distanceSq = entry.distanceToInViewSq(originalStart);
     278            if (distanceSq < 0.01 || strokeLength <= 0.001) {
     279                // don't move if there is nothing to move.
     280                return entry;
     281            }
     282
     283            double distance = Math.sqrt(distanceSq);
     284            double offset = ((strokeOffset + distance)) % strokeLength;
     285            if (offset < 0.01) {
     286                return entry;
     287            }
     288
     289            return entry.interpolate(originalStart, offset / distance);
     290        }
     291
     292    }
    193293}
Note: See TracChangeset for help on using the changeset viewer.