+/*******************************************************************************
+ * 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<Segment> 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<Double> 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);
+ }
+ }
+
+}