X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Felementclass%2Fconnection%2FEdgeSceneGraph.java;fp=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Felementclass%2Fconnection%2FEdgeSceneGraph.java;h=941b9272fe7df0bde5bfc75152f0543029cb1bd6;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hp=b12ad68685e66ae6495ce5cb20224de6744b66d4;hpb=24e2b34260f219f0d1644ca7a138894980e25b14;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java index b12ad6868..941b9272f 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java @@ -1,394 +1,394 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 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: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.g2d.elementclass.connection; - - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.AffineTransform; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; -import java.awt.geom.Path2D; -import java.awt.geom.PathIterator; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; - -import org.simantics.g2d.diagram.IDiagram; -import org.simantics.g2d.diagram.handler.Topology; -import org.simantics.g2d.diagram.handler.Topology.Connection; -import org.simantics.g2d.element.ElementUtils; -import org.simantics.g2d.element.IElement; -import org.simantics.g2d.element.SceneGraphNodeKey; -import org.simantics.g2d.element.handler.BendsHandler; -import org.simantics.g2d.element.handler.EdgeVisuals; -import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType; -import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; -import org.simantics.g2d.element.handler.Rotate; -import org.simantics.g2d.element.handler.SceneGraph; -import org.simantics.g2d.element.handler.TerminalLayout; -import org.simantics.g2d.elementclass.BranchPoint; -import org.simantics.g2d.utils.PathUtils; -import org.simantics.scenegraph.g2d.G2DParentNode; -import org.simantics.scenegraph.g2d.nodes.EdgeNode; -import org.simantics.utils.datastructures.hints.IHintContext.Key; - -/** - * Generic edge painter - * - * @author J-P Laine - */ -public class EdgeSceneGraph implements SceneGraph { - - private static final long serialVersionUID = 2914383071126238996L; - - public static final EdgeSceneGraph INSTANCE = new EdgeSceneGraph(); - - public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - - public static final Key KEY_SG_NODE = new SceneGraphNodeKey(EdgeNode.class, "EDGE_SG_NODE"); - - @Override - public void init(IElement e, G2DParentNode parent) { - ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), EdgeNode.class); - update(e); - } - - @Override - public void cleanup(IElement e) { - ElementUtils.removePossibleNode(e, KEY_SG_NODE); - } - - public void update(final IElement e) { - EdgeNode node = e.getHint(KEY_SG_NODE); - if(node == null) return; - - EdgeVisuals vh = e.getElementClass().getSingleItem(EdgeVisuals.class); - ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin); - ArrowType at2 = vh.getArrowType(e, EdgeEnd.End); - Stroke stroke = vh.getStroke(e); - //StrokeType strokeType = vh.getStrokeType(e); - double as1 = vh.getArrowSize(e, EdgeEnd.Begin); - double as2 = vh.getArrowSize(e, EdgeEnd.End); - - Color c = ElementUtils.getFillColor(e, Color.BLACK); - - // Get terminal shape for clipping the painted edge to its bounds. - IDiagram diagram = ElementUtils.peekDiagram(e); - Shape beginTerminalShape = null; - Shape endTerminalShape = null; - if (diagram != null) { - Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class); - if (topology != null) { - Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin); - Connection endConnection = topology.getConnection(e, EdgeEnd.End); - beginTerminalShape = getTerminalShape(beginConnection); - endTerminalShape = getTerminalShape(endConnection); - int beginBranchDegree = getBranchPointDegree(beginConnection, topology); - int endBranchDegree = getBranchPointDegree(endConnection, topology); - if (beginBranchDegree > 0 && beginBranchDegree < 3) { - at1 = ArrowType.None; - } - if (endBranchDegree > 0 && endBranchDegree < 3) { - at2 = ArrowType.None; - } - } - } - - // Read bends - BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class); - Path2D line = bh.getPath(e); - - boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None; - //line = clipLineEnds(line, beginTerminalShape, endTerminalShape); - - Point2D first = new Point2D.Double(); - Point2D dir1 = new Point2D.Double(); - Point2D last = new Point2D.Double(); - Point2D dir2 = new Point2D.Double(); - PathIterator pi = line.getPathIterator(null); - drawArrows &= PathUtils.getPathArrows(pi, first, dir1, last, dir2); - - if (drawArrows) { - line = trimLineToArrows(line, at1, as1, at2, as2); - } - - EdgeNode.ArrowType pat1 = convert(at1); - EdgeNode.ArrowType pat2 = convert(at2); - - node.init(new GeneralPath(line), stroke, c, dir1, dir2, first, last, as1, as2, pat1, pat2, null, null); - } - - private static EdgeNode.ArrowType convert(ArrowType at) { - switch (at) { - case None: return EdgeNode.ArrowType.None; - case Stroke: return EdgeNode.ArrowType.Stroke; - case Fill: return EdgeNode.ArrowType.Fill; - default: - throw new IllegalArgumentException("unsupported arrow type: " + at); - } - } - - private static final Rectangle2D EMPTY = new Rectangle2D.Double(); - - private static Shape getTerminalShape(Connection connection) { - if (connection != null && connection.node != null && connection.terminal != null) { - TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class); - if (layout != null) { - //return layout.getTerminalShape(connection.node, connection.terminal); - Shape shp = layout.getTerminalShape(connection.node, connection.terminal); - Rotate rotate = connection.node.getElementClass().getAtMostOneItemOfClass(Rotate.class); - if (rotate == null) - return shp; - - double theta = rotate.getAngle(connection.node); - return AffineTransform.getRotateInstance(theta).createTransformedShape(shp); - } - } - return null; - } - - private final Collection connectionsTemp = new ArrayList(); - private int getBranchPointDegree(Connection connection, Topology topology) { - if (connection != null && connection.node != null) { - if (connection.node.getElementClass().containsClass(BranchPoint.class)) { - connectionsTemp.clear(); - topology.getConnections(connection.node, connection.terminal, connectionsTemp); - int degree = connectionsTemp.size(); - connectionsTemp.clear(); - return degree; - } - } - return -1; - } - - private static Path2D clipLineEnds(Path2D line, Shape beginTerminalShape, Shape endTerminalShape) { - if (beginTerminalShape == null && endTerminalShape == null) - return line; - - Rectangle2D bb = beginTerminalShape != null ? beginTerminalShape.getBounds2D() : EMPTY; - Rectangle2D eb = endTerminalShape != null ? endTerminalShape.getBounds2D() : EMPTY; - // If the terminal shape doesn't contain its own coordinate system - // origin, just ignore the terminal shape. - if (bb != EMPTY && !bb.contains(0, 0)) - bb = EMPTY; - if (eb != EMPTY && !eb.contains(0, 0)) - eb = EMPTY; - if (bb.isEmpty() && eb.isEmpty()) - return line; - - Path2D result = new Path2D.Double(); - - PathIterator pi = line.getPathIterator(null); - Iterator it = PathUtils.toLineIterator(pi); - boolean first = true; - while (it.hasNext()) { - double[] seg = it.next(); - int degree = PathUtils.getLineDegree(seg); - //System.out.println("SEG: " + Arrays.toString(seg)); - - if (first) { - first = false; - Point2D start = PathUtils.getLinePos(seg, 0); - Point2D sp = clipToRectangle(bb, PathUtils.getLinePos(seg, 1), start); - if (sp != null) { - result.moveTo(sp.getX(), sp.getY()); - } else { - result.moveTo(start.getX(), start.getY()); - } - } - if (!it.hasNext()) { - // this is the last segment - Point2D ep = clipToRectangle(eb, PathUtils.getLinePos(seg, 0), PathUtils.getLinePos(seg, 1)); - //System.out.println("EP: " + ep + ", " + PathUtils.getLinePos(seg, 0) + " -> " + PathUtils.getLinePos(seg, 1)); - if (ep != null) { - seg[degree * 2] = ep.getX(); - seg[degree * 2 + 1] = ep.getY(); - } - } - - if (degree == 1) { - result.lineTo(seg[2], seg[3]); - } else if (degree == 2) { - result.quadTo(seg[2], seg[3], seg[4], seg[5]); - } else if (degree == 3) { - result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]); - } else { - throw new UnsupportedOperationException("invalid path segment degree: " + degree); - } - } - - result.setWindingRule(line.getWindingRule()); - return result; - } - - private static Path2D trimLineToArrows(Path2D line, ArrowType beginArrowType, double beginArrowSize, ArrowType endArrowType, double endArrowSize) { - Path2D result = new Path2D.Double(); - PathIterator pi = line.getPathIterator(null); - Iterator it = PathUtils.toLineIterator(pi); - boolean first = true; - - while (it.hasNext()) { - double[] seg = it.next(); - int degree = PathUtils.getLineDegree(seg); - - if (first) { - first = false; - - if (beginArrowType == ArrowType.Fill) { - Point2D t = PathUtils.getLineTangent(seg, 0); - double len = Math.sqrt(lensq(t, null)); - if (len > beginArrowSize) { - double scale = beginArrowSize / len; - seg[0] += t.getX() * scale; - seg[1] += t.getY() * scale; - } else { - // Remove the first segment completely if the segment is too - // small to be noticed from under the arrow. - result.moveTo(seg[degree * 2], seg[degree * 2 + 1]); - continue; - } - } - - result.moveTo(seg[0], seg[1]); - } - if (!it.hasNext()) { - // this is the last segment - if (endArrowType == ArrowType.Fill) { - Point2D t = PathUtils.getLineTangent(seg, 1); - double len = Math.sqrt(lensq(t, null)); - if (len > endArrowSize) { - double scale = endArrowSize / len; - seg[degree * 2] -= t.getX() * scale; - seg[degree * 2 + 1] -= t.getY() * scale; - } - } - } - - if (degree == 1) { - result.lineTo(seg[2], seg[3]); - } else if (degree == 2) { - result.quadTo(seg[2], seg[3], seg[4], seg[5]); - } else if (degree == 3) { - result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]); - } else { - throw new UnsupportedOperationException("invalid path segment degree: " + degree); - } - } - - result.setWindingRule(line.getWindingRule()); - return result; - } - - private static Point2D clipToRectangle(Rectangle2D bounds, Point2D p1, Point2D p2) { - if (bounds.isEmpty()) - return p2; - - Line2D line = new Line2D.Double(p1, p2); - Point2D vi1 = intersectWithHorizontalLine(line, bounds.getMinY() + p2.getY()); - Point2D vi2 = intersectWithHorizontalLine(line, bounds.getMaxY() + p2.getY()); - Point2D hi1 = intersectWithVerticalLine(line, bounds.getMinX() + p2.getX()); - Point2D hi2 = intersectWithVerticalLine(line, bounds.getMaxX() + p2.getX()); - - int i = 0; - Point2D[] intersections = { null, null, null, null }; - if (vi1 != null) - intersections[i++] = vi1; - if (vi2 != null) - intersections[i++] = vi2; - if (hi1 != null) - intersections[i++] = hi1; - if (hi2 != null) - intersections[i++] = hi2; - - //System.out.println(bounds + ": P1(" + p1 + ") - P2(" + p2 +"): " + Arrays.toString(intersections)); - - if (i == 0) - return p2; - if (i == 1) - return intersections[0]; - - // Find the intersection i for which applies - // lensq(p1, p2) >= lensq(p1, i) & - // for all intersections j != i: lensq(p1, i) > lensq(p1, j) - double len = lensq(p1, p2); - //System.out.println("original line lensq: " + len); - Point2D nearestIntersection = null; - double nearestLen = -1; - for (int j = 0; j < i; ++j) { - double l = lensq(p1, intersections[j]); - //System.out.println("intersected line lensq: " + l); - if (l <= len && l > nearestLen) { - nearestIntersection = intersections[j]; - nearestLen = l; - //System.out.println("nearest"); - } - } - return nearestIntersection; - } - - private static double lensq(Point2D p1, Point2D p2) { - double dx = p1.getX(); - double dy = p1.getY(); - if (p2 != null) { - dx = p2.getX() - dx; - dy = p2.getY() - dy; - } - return dx*dx + dy*dy; - } - - private static Point2D intersectWithHorizontalLine(Line2D l, double y) { - double dx = l.getX2() - l.getX1(); - double dy = l.getY2() - l.getY1(); - - if (Math.abs(dy) < 1e-5) { - // input line as horizontal, no intersection. - return null; - } - double a = dx / dy; - return new Point2D.Double((y - l.getY1()) * a + l.getX1(), y); - } - - private static Point2D intersectWithVerticalLine(Line2D l, double x) { - double dx = l.getX2() - l.getX1(); - double dy = l.getY2() - l.getY1(); - - if (Math.abs(dx) < 1e-5) { - // input line as vertical, no intersection. - return null; - } - double a = dy / dx; - return new Point2D.Double(x, a * (x - l.getX1()) + l.getY1()); - } - - - public final static Path2D NORMAL_ARROW; - public final static Path2D FILLED_ARROW; - - static { - FILLED_ARROW = new Path2D.Double(); - FILLED_ARROW.moveTo(-0.5, 1); - FILLED_ARROW.lineTo( 0, 0); - FILLED_ARROW.lineTo( 0.5, 1); - FILLED_ARROW.closePath(); - - NORMAL_ARROW = new Path2D.Double(); - NORMAL_ARROW.moveTo(-0.5, 1); - NORMAL_ARROW.lineTo( 0, 0); - NORMAL_ARROW.lineTo( 0.5, 1); - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 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: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.elementclass.connection; + + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import org.simantics.g2d.diagram.IDiagram; +import org.simantics.g2d.diagram.handler.Topology; +import org.simantics.g2d.diagram.handler.Topology.Connection; +import org.simantics.g2d.element.ElementUtils; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.SceneGraphNodeKey; +import org.simantics.g2d.element.handler.BendsHandler; +import org.simantics.g2d.element.handler.EdgeVisuals; +import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType; +import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; +import org.simantics.g2d.element.handler.Rotate; +import org.simantics.g2d.element.handler.SceneGraph; +import org.simantics.g2d.element.handler.TerminalLayout; +import org.simantics.g2d.elementclass.BranchPoint; +import org.simantics.g2d.utils.PathUtils; +import org.simantics.scenegraph.g2d.G2DParentNode; +import org.simantics.scenegraph.g2d.nodes.EdgeNode; +import org.simantics.utils.datastructures.hints.IHintContext.Key; + +/** + * Generic edge painter + * + * @author J-P Laine + */ +public class EdgeSceneGraph implements SceneGraph { + + private static final long serialVersionUID = 2914383071126238996L; + + public static final EdgeSceneGraph INSTANCE = new EdgeSceneGraph(); + + public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); + + public static final Key KEY_SG_NODE = new SceneGraphNodeKey(EdgeNode.class, "EDGE_SG_NODE"); + + @Override + public void init(IElement e, G2DParentNode parent) { + ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), EdgeNode.class); + update(e); + } + + @Override + public void cleanup(IElement e) { + ElementUtils.removePossibleNode(e, KEY_SG_NODE); + } + + public void update(final IElement e) { + EdgeNode node = e.getHint(KEY_SG_NODE); + if(node == null) return; + + EdgeVisuals vh = e.getElementClass().getSingleItem(EdgeVisuals.class); + ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin); + ArrowType at2 = vh.getArrowType(e, EdgeEnd.End); + Stroke stroke = vh.getStroke(e); + //StrokeType strokeType = vh.getStrokeType(e); + double as1 = vh.getArrowSize(e, EdgeEnd.Begin); + double as2 = vh.getArrowSize(e, EdgeEnd.End); + + Color c = ElementUtils.getFillColor(e, Color.BLACK); + + // Get terminal shape for clipping the painted edge to its bounds. + IDiagram diagram = ElementUtils.peekDiagram(e); + Shape beginTerminalShape = null; + Shape endTerminalShape = null; + if (diagram != null) { + Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class); + if (topology != null) { + Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin); + Connection endConnection = topology.getConnection(e, EdgeEnd.End); + beginTerminalShape = getTerminalShape(beginConnection); + endTerminalShape = getTerminalShape(endConnection); + int beginBranchDegree = getBranchPointDegree(beginConnection, topology); + int endBranchDegree = getBranchPointDegree(endConnection, topology); + if (beginBranchDegree > 0 && beginBranchDegree < 3) { + at1 = ArrowType.None; + } + if (endBranchDegree > 0 && endBranchDegree < 3) { + at2 = ArrowType.None; + } + } + } + + // Read bends + BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class); + Path2D line = bh.getPath(e); + + boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None; + //line = clipLineEnds(line, beginTerminalShape, endTerminalShape); + + Point2D first = new Point2D.Double(); + Point2D dir1 = new Point2D.Double(); + Point2D last = new Point2D.Double(); + Point2D dir2 = new Point2D.Double(); + PathIterator pi = line.getPathIterator(null); + drawArrows &= PathUtils.getPathArrows(pi, first, dir1, last, dir2); + + if (drawArrows) { + line = trimLineToArrows(line, at1, as1, at2, as2); + } + + EdgeNode.ArrowType pat1 = convert(at1); + EdgeNode.ArrowType pat2 = convert(at2); + + node.init(new GeneralPath(line), stroke, c, dir1, dir2, first, last, as1, as2, pat1, pat2, null, null); + } + + private static EdgeNode.ArrowType convert(ArrowType at) { + switch (at) { + case None: return EdgeNode.ArrowType.None; + case Stroke: return EdgeNode.ArrowType.Stroke; + case Fill: return EdgeNode.ArrowType.Fill; + default: + throw new IllegalArgumentException("unsupported arrow type: " + at); + } + } + + private static final Rectangle2D EMPTY = new Rectangle2D.Double(); + + private static Shape getTerminalShape(Connection connection) { + if (connection != null && connection.node != null && connection.terminal != null) { + TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class); + if (layout != null) { + //return layout.getTerminalShape(connection.node, connection.terminal); + Shape shp = layout.getTerminalShape(connection.node, connection.terminal); + Rotate rotate = connection.node.getElementClass().getAtMostOneItemOfClass(Rotate.class); + if (rotate == null) + return shp; + + double theta = rotate.getAngle(connection.node); + return AffineTransform.getRotateInstance(theta).createTransformedShape(shp); + } + } + return null; + } + + private final Collection connectionsTemp = new ArrayList(); + private int getBranchPointDegree(Connection connection, Topology topology) { + if (connection != null && connection.node != null) { + if (connection.node.getElementClass().containsClass(BranchPoint.class)) { + connectionsTemp.clear(); + topology.getConnections(connection.node, connection.terminal, connectionsTemp); + int degree = connectionsTemp.size(); + connectionsTemp.clear(); + return degree; + } + } + return -1; + } + + private static Path2D clipLineEnds(Path2D line, Shape beginTerminalShape, Shape endTerminalShape) { + if (beginTerminalShape == null && endTerminalShape == null) + return line; + + Rectangle2D bb = beginTerminalShape != null ? beginTerminalShape.getBounds2D() : EMPTY; + Rectangle2D eb = endTerminalShape != null ? endTerminalShape.getBounds2D() : EMPTY; + // If the terminal shape doesn't contain its own coordinate system + // origin, just ignore the terminal shape. + if (bb != EMPTY && !bb.contains(0, 0)) + bb = EMPTY; + if (eb != EMPTY && !eb.contains(0, 0)) + eb = EMPTY; + if (bb.isEmpty() && eb.isEmpty()) + return line; + + Path2D result = new Path2D.Double(); + + PathIterator pi = line.getPathIterator(null); + Iterator it = PathUtils.toLineIterator(pi); + boolean first = true; + while (it.hasNext()) { + double[] seg = it.next(); + int degree = PathUtils.getLineDegree(seg); + //System.out.println("SEG: " + Arrays.toString(seg)); + + if (first) { + first = false; + Point2D start = PathUtils.getLinePos(seg, 0); + Point2D sp = clipToRectangle(bb, PathUtils.getLinePos(seg, 1), start); + if (sp != null) { + result.moveTo(sp.getX(), sp.getY()); + } else { + result.moveTo(start.getX(), start.getY()); + } + } + if (!it.hasNext()) { + // this is the last segment + Point2D ep = clipToRectangle(eb, PathUtils.getLinePos(seg, 0), PathUtils.getLinePos(seg, 1)); + //System.out.println("EP: " + ep + ", " + PathUtils.getLinePos(seg, 0) + " -> " + PathUtils.getLinePos(seg, 1)); + if (ep != null) { + seg[degree * 2] = ep.getX(); + seg[degree * 2 + 1] = ep.getY(); + } + } + + if (degree == 1) { + result.lineTo(seg[2], seg[3]); + } else if (degree == 2) { + result.quadTo(seg[2], seg[3], seg[4], seg[5]); + } else if (degree == 3) { + result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]); + } else { + throw new UnsupportedOperationException("invalid path segment degree: " + degree); + } + } + + result.setWindingRule(line.getWindingRule()); + return result; + } + + private static Path2D trimLineToArrows(Path2D line, ArrowType beginArrowType, double beginArrowSize, ArrowType endArrowType, double endArrowSize) { + Path2D result = new Path2D.Double(); + PathIterator pi = line.getPathIterator(null); + Iterator it = PathUtils.toLineIterator(pi); + boolean first = true; + + while (it.hasNext()) { + double[] seg = it.next(); + int degree = PathUtils.getLineDegree(seg); + + if (first) { + first = false; + + if (beginArrowType == ArrowType.Fill) { + Point2D t = PathUtils.getLineTangent(seg, 0); + double len = Math.sqrt(lensq(t, null)); + if (len > beginArrowSize) { + double scale = beginArrowSize / len; + seg[0] += t.getX() * scale; + seg[1] += t.getY() * scale; + } else { + // Remove the first segment completely if the segment is too + // small to be noticed from under the arrow. + result.moveTo(seg[degree * 2], seg[degree * 2 + 1]); + continue; + } + } + + result.moveTo(seg[0], seg[1]); + } + if (!it.hasNext()) { + // this is the last segment + if (endArrowType == ArrowType.Fill) { + Point2D t = PathUtils.getLineTangent(seg, 1); + double len = Math.sqrt(lensq(t, null)); + if (len > endArrowSize) { + double scale = endArrowSize / len; + seg[degree * 2] -= t.getX() * scale; + seg[degree * 2 + 1] -= t.getY() * scale; + } + } + } + + if (degree == 1) { + result.lineTo(seg[2], seg[3]); + } else if (degree == 2) { + result.quadTo(seg[2], seg[3], seg[4], seg[5]); + } else if (degree == 3) { + result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]); + } else { + throw new UnsupportedOperationException("invalid path segment degree: " + degree); + } + } + + result.setWindingRule(line.getWindingRule()); + return result; + } + + private static Point2D clipToRectangle(Rectangle2D bounds, Point2D p1, Point2D p2) { + if (bounds.isEmpty()) + return p2; + + Line2D line = new Line2D.Double(p1, p2); + Point2D vi1 = intersectWithHorizontalLine(line, bounds.getMinY() + p2.getY()); + Point2D vi2 = intersectWithHorizontalLine(line, bounds.getMaxY() + p2.getY()); + Point2D hi1 = intersectWithVerticalLine(line, bounds.getMinX() + p2.getX()); + Point2D hi2 = intersectWithVerticalLine(line, bounds.getMaxX() + p2.getX()); + + int i = 0; + Point2D[] intersections = { null, null, null, null }; + if (vi1 != null) + intersections[i++] = vi1; + if (vi2 != null) + intersections[i++] = vi2; + if (hi1 != null) + intersections[i++] = hi1; + if (hi2 != null) + intersections[i++] = hi2; + + //System.out.println(bounds + ": P1(" + p1 + ") - P2(" + p2 +"): " + Arrays.toString(intersections)); + + if (i == 0) + return p2; + if (i == 1) + return intersections[0]; + + // Find the intersection i for which applies + // lensq(p1, p2) >= lensq(p1, i) & + // for all intersections j != i: lensq(p1, i) > lensq(p1, j) + double len = lensq(p1, p2); + //System.out.println("original line lensq: " + len); + Point2D nearestIntersection = null; + double nearestLen = -1; + for (int j = 0; j < i; ++j) { + double l = lensq(p1, intersections[j]); + //System.out.println("intersected line lensq: " + l); + if (l <= len && l > nearestLen) { + nearestIntersection = intersections[j]; + nearestLen = l; + //System.out.println("nearest"); + } + } + return nearestIntersection; + } + + private static double lensq(Point2D p1, Point2D p2) { + double dx = p1.getX(); + double dy = p1.getY(); + if (p2 != null) { + dx = p2.getX() - dx; + dy = p2.getY() - dy; + } + return dx*dx + dy*dy; + } + + private static Point2D intersectWithHorizontalLine(Line2D l, double y) { + double dx = l.getX2() - l.getX1(); + double dy = l.getY2() - l.getY1(); + + if (Math.abs(dy) < 1e-5) { + // input line as horizontal, no intersection. + return null; + } + double a = dx / dy; + return new Point2D.Double((y - l.getY1()) * a + l.getX1(), y); + } + + private static Point2D intersectWithVerticalLine(Line2D l, double x) { + double dx = l.getX2() - l.getX1(); + double dy = l.getY2() - l.getY1(); + + if (Math.abs(dx) < 1e-5) { + // input line as vertical, no intersection. + return null; + } + double a = dy / dx; + return new Point2D.Double(x, a * (x - l.getX1()) + l.getY1()); + } + + + public final static Path2D NORMAL_ARROW; + public final static Path2D FILLED_ARROW; + + static { + FILLED_ARROW = new Path2D.Double(); + FILLED_ARROW.moveTo(-0.5, 1); + FILLED_ARROW.lineTo( 0, 0); + FILLED_ARROW.lineTo( 0.5, 1); + FILLED_ARROW.closePath(); + + NORMAL_ARROW = new Path2D.Double(); + NORMAL_ARROW.moveTo(-0.5, 1); + NORMAL_ARROW.lineTo( 0, 0); + NORMAL_ARROW.lineTo( 0.5, 1); + } + +}