1 /*******************************************************************************
2 * Copyright (c) 2020 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Semantum Oy - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.connection.rendering;
14 import java.awt.geom.Arc2D;
15 import java.awt.geom.Path2D;
16 import java.awt.geom.PathIterator;
17 import java.awt.geom.Point2D;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.List;
22 public class ConnectionCrossings implements PathModifier {
30 private List<Segment> segments = new ArrayList<>();
32 private double widthd2;
34 private boolean keepLines = false;
37 public void setWidth(double gapWidth) {
38 this.width = gapWidth;
39 this.widthd2 = width * 0.5;
42 public double getWidth() {
46 public void setType(Type type) {
50 public Type getType() {
55 * When keep lines is enabled, we keep short lines in the start and end of a line segment, instead of removing them completely with gaps.
58 public boolean isKeepLines() {
62 public void setKeepLines(boolean keepLines) {
63 this.keepLines = keepLines;
66 static class Segment {
67 public double x1, y1, x2, y2;
69 public Segment(double x1, double y1, double x2, double y2) {
81 static Double lineLineIntersection(Segment l1, Segment l2) {
82 double epsilon = 0.001;
84 double d = (l1.x1 - l1.x2) * (l2.y1 - l2.y2) - (l1.y1 - l1.y2) * (l2.x1 - l2.x2);
85 if (d == 0.0) return null;
86 double s = ((l1.x1 - l2.x1) * (l2.y1 - l2.y2) - (l1.y1 - l2.y1) * (l2.x1 - l2.x2)) / d;
87 if ((s > epsilon) && (s < 1 - epsilon)) {
88 double t = -((l1.x1 - l1.x2) * (l1.y1 - l2.y1) - (l1.y1 - l1.y2) * (l1.x1 - l2.x1)) / d;
89 if ((t > epsilon) && (t < 1 - epsilon)) {
96 public Path2D modify(Path2D path) {
97 Path2D.Double path2 = new Path2D.Double();
98 PathIterator iter = path.getPathIterator(null);
100 while (!iter.isDone()) {
102 double c[] = new double[6];
103 int i = iter.currentSegment(c);
105 case PathIterator.SEG_MOVETO:
106 path2.moveTo(c[0], c[1]);
108 case PathIterator.SEG_LINETO:
109 Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]);
111 List<Double> gaps = new ArrayList<>();
112 for (Segment old : segments) {
113 Double t = lineLineIntersection(old, l);
119 if (gaps.isEmpty()) {
120 path2.lineTo(c[0], c[1]);
122 Collections.sort(gaps);
123 double dx = l.x2 - l.x1;
124 double dy = l.y2 - l.y1;
127 double len = Math.sqrt(dx * dx + dy * dy);
128 double len1 = 1.0 / len;
130 boolean finish = true;
131 Point2D prevGapEnd = null;
132 for (Double gapCenter : gaps) {
133 double pos2 = gapCenter - widthd2 * len1;
134 double pos3 = gapCenter + widthd2 * len1;
136 handleGap(path2, prevGapEnd);
138 path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy);
141 double x = l.x1 + pos3 * dx;
142 double y = l.y1 + pos3 * dy;
143 prevGapEnd = new Point2D.Double(x, y);
151 handleGap(path2, prevGapEnd);
152 path2.lineTo(l.x2, l.y2);
154 prevGapEnd = new Point2D.Double(l.x2, l.y2);
155 handleGap(path2, prevGapEnd);
161 case PathIterator.SEG_QUADTO:
162 // TODO: implement gaps
163 path2.quadTo(c[0], c[1], c[2], c[3]);
165 case PathIterator.SEG_CUBICTO:
166 // TODO: implement gaps
167 path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
169 case PathIterator.SEG_CLOSE:
170 // TODO: implement gaps
174 throw new RuntimeException("Unexpected segment type " + i);
179 while (!iter.isDone()) {
181 double c[] = new double[6];
182 int i = iter.currentSegment(c);
184 case PathIterator.SEG_MOVETO:
185 path2.moveTo(c[0], c[1]);
187 case PathIterator.SEG_LINETO:
188 Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]);
190 List<Double> gaps = new ArrayList<>();
191 for (Segment old : segments) {
192 Double t = lineLineIntersection(old, l);
198 if (gaps.isEmpty()) {
199 path2.lineTo(c[0], c[1]);
201 Collections.sort(gaps);
202 double dx = l.x2 - l.x1;
203 double dy = l.y2 - l.y1;
206 double len = Math.sqrt(dx * dx + dy * dy);
207 double len1 = 1.0 / len;
209 boolean finish = true;
210 Point2D prevGapEnd = null;
211 for (int j = 0; j < gaps.size(); j++) {
212 Double gapCenter = gaps.get(j);
213 double gc = gapCenter * len;
214 double pos2 = gc - widthd2;
215 double pos3 = gc + widthd2;
218 pos2 += (widthd2 - pos2) * 0.5;
220 if (j == gaps.size() - 1) {
221 double d = len - pos3;
223 pos3 -= (widthd2 - d) * 0.5;
226 handleGap(path2, prevGapEnd);
229 path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy);
233 double x = l.x1 + pos3 * dx;
234 double y = l.y1 + pos3 * dy;
235 prevGapEnd = new Point2D.Double(x, y);
243 handleGap(path2, prevGapEnd);
244 path2.lineTo(l.x2, l.y2);
246 prevGapEnd = new Point2D.Double(l.x2, l.y2);
247 handleGap(path2, prevGapEnd);
253 case PathIterator.SEG_QUADTO:
254 // TODO: implement gaps
255 path2.quadTo(c[0], c[1], c[2], c[3]);
257 case PathIterator.SEG_CUBICTO:
258 // TODO: implement gaps
259 path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
261 case PathIterator.SEG_CLOSE:
262 // TODO: implement gaps
266 throw new RuntimeException("Unexpected segment type " + i);
274 private void handleGap(Path2D path, Point2D prevGapEnd) {
275 if (prevGapEnd != null) {
278 arcTo(path, prevGapEnd.getX(), prevGapEnd.getY());
281 squareTo(path, prevGapEnd.getX(), prevGapEnd.getY(), width);
284 path.moveTo(prevGapEnd.getX(), prevGapEnd.getY());
292 private static void arcTo(Path2D path, double x2, double y2) {
293 Arc2D arc = new Arc2D.Double();
294 double x1 = path.getCurrentPoint().getX();
295 double y1 = path.getCurrentPoint().getY();
298 double r = Math.sqrt(dx * dx + dy * dy) / 2;
299 double angle = Math.atan2(dx, dy) * 180 / Math.PI + 90;
300 double span = (angle > 225 || angle < 45) ? 180 : -180;
301 arc.setArcByCenter((x1 + x2) / 2, (y1 + y2) / 2, r, angle, span, Arc2D.OPEN);
302 path.append(arc, true);
305 private static void squareTo(Path2D path, double x2, double y2, double width) {
306 double x1 = path.getCurrentPoint().getX();
307 double y1 = path.getCurrentPoint().getY();
310 double l = Math.sqrt(dx * dx + dy* dy);
318 path.lineTo(x1 + nx * width / 2, y1 + ny * width / 2);
319 path.lineTo(x2 + nx * width / 2, y2 + ny * width / 2);