/******************************************************************************* * Copyright (c) 2020 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Semantum Oy - initial API and implementation *******************************************************************************/ package org.simantics.diagram.connection.rendering; import java.awt.geom.Arc2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ConnectionCrossings implements PathModifier { public enum Type { NONE, GAP, ARC, SQUARE } private List segments = new ArrayList<>(); private double width; private Type type; public void setWidth(double gapWidth) { this.width = gapWidth; } public double getWidth() { return width; } public void setType(Type type) { this.type = type; } public Type getType() { return type; } static class Segment { public double x1, y1, x2, y2; public Segment(double x1, double y1, double x2, double y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } }; public void reset() { segments.clear(); } static Double lineLineIntersection(Segment l1, Segment l2) { double epsilon = 0.001; double d = (l1.x1 - l1.x2) * (l2.y1 - l2.y2) - (l1.y1 - l1.y2) * (l2.x1 - l2.x2); if (d == 0.0) return null; double s = ((l1.x1 - l2.x1) * (l2.y1 - l2.y2) - (l1.y1 - l2.y1) * (l2.x1 - l2.x2)) / d; if ((s > epsilon) && (s < 1 - epsilon)) { double t = -((l1.x1 - l1.x2) * (l1.y1 - l2.y1) - (l1.y1 - l1.y2) * (l1.x1 - l2.x1)) / d; if ((t > epsilon) && (t < 1 - epsilon)) { return t; } } return null; } public Path2D modify(Path2D path) { Path2D.Double path2 = new Path2D.Double(); PathIterator iter = path.getPathIterator(null); while (!iter.isDone()) { double c[] = new double[6]; int i = iter.currentSegment(c); switch (i) { case PathIterator.SEG_MOVETO: path2.moveTo(c[0], c[1]); break; case PathIterator.SEG_LINETO: Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]); List gaps = new ArrayList<>(); for (Segment old : segments) { Double t = lineLineIntersection(old, l); if (t != null) { gaps.add(t); } } if (gaps.isEmpty()) { path2.lineTo(c[0], c[1]); } else { Collections.sort(gaps); double dx = l.x2 - l.x1; double dy = l.y2 - l.y1; double pos = 0.0; double len = Math.sqrt(dx*dx + dy*dy); boolean finish = true; Point2D prevGapEnd = null; for (Double gapCenter : gaps) { double pos2 = gapCenter - width / 2 / len; double pos3 = gapCenter + width / 2 / len; if (pos2 > pos) { handleGap(path2, prevGapEnd); prevGapEnd = null; path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy); } if (pos3 < 1.0) { double x = l.x1 + pos3 * dx; double y = l.y1 + pos3 * dy; prevGapEnd = new Point2D.Double(x, y); } else { finish = false; } pos = pos3; } if (finish) { handleGap(path2, prevGapEnd); path2.lineTo(l.x2, l.y2); } else { prevGapEnd = new Point2D.Double(l.x2, l.y2); handleGap(path2, prevGapEnd); } } segments.add(l); break; case PathIterator.SEG_QUADTO: // TODO: implement gaps path2.quadTo(c[0], c[1], c[2], c[3]); break; case PathIterator.SEG_CUBICTO: // TODO: implement gaps path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]); break; case PathIterator.SEG_CLOSE: // TODO: implement gaps path2.closePath(); break; default: throw new RuntimeException("Unexpected segment type " + i); } iter.next(); } return path2; } private void handleGap(Path2D path, Point2D prevGapEnd) { if (prevGapEnd != null) { switch (type) { case ARC: arcTo(path, prevGapEnd.getX(), prevGapEnd.getY()); break; case SQUARE: squareTo(path, prevGapEnd.getX(), prevGapEnd.getY(), width); break; case GAP: path.moveTo(prevGapEnd.getX(), prevGapEnd.getY()); break; case NONE: break; } } } private static void arcTo(Path2D path, double x2, double y2) { Arc2D arc = new Arc2D.Double(); double x1 = path.getCurrentPoint().getX(); double y1 = path.getCurrentPoint().getY(); double dx = x2 - x1; double dy = y2 - y1; double r = Math.sqrt(dx * dx + dy * dy) / 2; double angle = Math.atan2(dx, dy) * 180 / Math.PI + 90; double span = (angle > 225 || angle < 45) ? 180 : -180; arc.setArcByCenter((x1 + x2) / 2, (y1 + y2) / 2, r, angle, span, Arc2D.OPEN); path.append(arc, true); } private static void squareTo(Path2D path, double x2, double y2, double width) { double x1 = path.getCurrentPoint().getX(); double y1 = path.getCurrentPoint().getY(); double dx = x2 - x1; double dy = y2 - y1; double l = Math.sqrt(dx * dx + dy* dy); if (l > 0) { double nx = -dy / l; double ny = dx / l; if (nx - ny < 0) { nx = -nx; ny = -ny; } path.lineTo(x1 + nx * width / 2, y1 + ny * width / 2); path.lineTo(x2 + nx * width / 2, y2 + ny * width / 2); path.lineTo(x2, y2); } } }