Mercurial > hg > ccmieditor
view java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java @ 8:ea7885bd9bff tip
fixed bug : render solid line as dotted/dashed when moving the stylus from dotted/dashed to solid
author | ccmi-guest |
---|---|
date | Thu, 03 Jul 2014 16:12:20 +0100 |
parents | d66dd5880081 |
children |
line wrap: on
line source
/* CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com) Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.qmul.eecs.ccmi.gui; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.ResourceBundle; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException; import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge; import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode; import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent; import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager; /** * An edge in a graph. Edge objects are used in a GraphPanel to render a diagram edge visually. * Edge objects are used in the tree representation of the diagram as well, as they're * subclasses of {@link DiagramEdge} * */ @SuppressWarnings("serial") public abstract class Edge extends DiagramEdge implements GraphElement{ /** * Creates a new Edge * * @param type the type of the edge. The type is just a string that the user assign to the edge. * All the edges created via clonation from this edge will have the same type. * @param availableEndDescriptions all the possible end description ends of this edge can be * associated to. An end description is a text associated to a possible arrow head of an edge end. * It's used to give awareness of arrow heads via speech. * @param minAttachedNodes the minimum number of nodes that can be attached to this edge * @param maxAttachedNodes the minimum number of nodes that can be attached to this edge * @param style the style of the edge: whether it's solid, dotted or dashed */ public Edge(String type, String[] availableEndDescriptions, int minAttachedNodes, int maxAttachedNodes,LineStyle style){ super(type,availableEndDescriptions); this.minAttachedNodes = minAttachedNodes; this.maxAttachedNodes = maxAttachedNodes; this.style = style; nodes = new ArrayList<Node>(); } /* --- Methods inherited from DiagramEdge --- */ @Override public Node getNodeAt(int index){ return nodes.get(index); } @Override public int getNodesNum(){ return nodes.size(); } @Override public boolean removeNode(DiagramNode diagramNode, Object source){ Node n = (Node)diagramNode; if(nodes.size() == 2) throw new RuntimeException("Cannot remove a node from a two ends edge"); else{ for(InnerPoint p : points) if(p.hasNeighbour(n)){ p.neighbours.remove(n); if(p.neighbours.size() == 1) removePoint(p); break; } boolean nodeRemoved = nodes.remove(n); /* for update in the haptic device */ notifyChange(new ElementChangedEvent(this,n,"remove_node",source)); return nodeRemoved; } } @Override public void connect(List<DiagramNode> nodes) throws ConnectNodesException{ assert(getNodesNum() == 0); /* this is to eliminate duplicates */ LinkedHashSet<Node> nodeSet = new LinkedHashSet<Node>(); for(DiagramNode n : nodes) nodeSet.add((Node)n); /* checks on connection consistency */ if((nodeSet == null)||(nodeSet.size() < minAttachedNodes)) throw new ConnectNodesException("You must select at least "+ minAttachedNodes + "nodes"); if((nodeSet.size() > maxAttachedNodes)) throw new ConnectNodesException("You must select at most " + maxAttachedNodes +" nodes"); points = new ArrayList<InnerPoint>(); if(nodeSet.size() > 2){ /* there are more than three nodes. compute the central inner point * * which will connect all the nodes, as the middle points of the edge bound */ Rectangle2D bounds = new Rectangle2D.Double(); for(Node n : nodeSet){ bounds.add(n.getBounds()); } InnerPoint p = new InnerPoint(); p.translate(new Point2D.Double(0,0), bounds.getCenterX(), bounds.getCenterY(),DiagramEventSource.NONE); p.neighbours.addAll(nodeSet); points.add(p); } this.nodes.addAll(nodeSet); if(!points.isEmpty())// points is empty when the edge has two nodes only masterInnerPoint = points.get(0); } @Override public abstract void draw(Graphics2D g2); @Override public void translate(Point2D p, double dx, double dy, Object source){ for(InnerPoint ip : points) ip.translate(p, dx, dy,source); } /** * To be called before {@code bend}, determines from {@code downPoint} whether * a line is going to be break into two lines (with the creation of a new inner point) * or if the bending is determined by an already existing inner point being translated */ @Override public void startMove(Point2D downPoint,Object source){ this.downPoint = downPoint; newInnerPoint = null; for(InnerPoint itrPoint : points) if(itrPoint.contains(downPoint)){ /* clicked on an already existing EdgePoint */ newInnerPoint = itrPoint; newPointCreated = false; } if(newInnerPoint == null){ /* no point under the click, create a new one */ newInnerPoint = new InnerPoint(); newInnerPoint.translate(downPoint, downPoint.getX() - newInnerPoint.getBounds().getCenterX(), downPoint.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE); newPointCreated = true; /* this methods checks for segments of the edge which are aligned and makes a unique edge out of them */ } } /** * If this edge is made out of several lines and two or more of them becomes * aligned then they are blended in one single line and the inner point that was * at the joint is removed. * * @param source the source of the {@code stopMove} action */ @Override public void stopMove(Object source){ for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){ InnerPoint ePoint = pItr.next(); if(ePoint.neighbours.size() > 2) continue; Rectangle2D startBounds = ePoint.getBounds(); Rectangle2D endBounds = ePoint.neighbours.get(0).getBounds(); Direction d1 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY()); endBounds = ePoint.neighbours.get(1).getBounds(); Direction d2 = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY()); if(d1.isParallel(d2)){ InnerPoint p = null; GraphElement q = null; if(ePoint.neighbours.get(0) instanceof InnerPoint){ p = (InnerPoint)ePoint.neighbours.get(0); q = ePoint.neighbours.get(1); p.neighbours.add(q); p.neighbours.remove(ePoint); } if(ePoint.neighbours.get(1) instanceof InnerPoint){ p = (InnerPoint)ePoint.neighbours.get(1); q = ePoint.neighbours.get(0); p.neighbours.add(q); p.neighbours.remove(ePoint); } pItr.remove(); } } notifyChange(new ElementChangedEvent(this,this,"stop_move",source)); } @Override public abstract Rectangle2D getBounds(); @Override public Point2D getConnectionPoint(Direction d){return null;} @Override public boolean contains(Point2D aPoint){ if(points.isEmpty()){ return fatStrokeContains (nodes.get(0), nodes.get(1), aPoint); } for(InnerPoint p : points){ for(GraphElement ge : p.neighbours){ if(fatStrokeContains(p,ge,aPoint)) return true; } } return false; } /** * Look for the node attached to this edge which lays at the minimum distance * from the point passed as argument. The distance cannot be lower than the * value passed as argument. * * @param aPoint the point the distance is measured from * @param distanceLimit the limit from the distance between the nodes and the point * @return the closest node or null if the node lays at an higher distance than distanceLimit */ public Node getClosestNode(Point2D aPoint, double distanceLimit){ Node closestNode = null; double minDist = distanceLimit; if(points.isEmpty()){ Line2D line = getSegment(nodes.get(0),nodes.get(1)); if(line.getP1().distance(aPoint) < minDist){ minDist = line.getP1().distance(aPoint); closestNode = nodes.get(0); } if(line.getP2().distance(aPoint) < minDist){ minDist = line.getP2().distance(aPoint); closestNode = nodes.get(1); } return closestNode; }else{ for(InnerPoint p : points){ for(GraphElement ge : p.getNeighbours()) if(ge instanceof Node){ Node n = (Node)ge; Direction d = new Direction(p.getBounds().getCenterX() - n.getBounds().getCenterX(),p.getBounds().getCenterY() - n.getBounds().getCenterY()); if(n.getConnectionPoint(d).distance(aPoint) < minDist){ minDist = n.getConnectionPoint(d).distance(aPoint); closestNode = n; } } } return closestNode; } } private void removePoint(InnerPoint p){ /* we assume at this moment p has one neighbour only */ InnerPoint neighbour = (InnerPoint)p.neighbours.get(0); points.remove(p); neighbour.neighbours.remove(p); if(neighbour.neighbours.size() == 1) removePoint(neighbour); } /* checks if a point belongs to a shape with a margin of MAX_DIST*/ private boolean fatStrokeContains(GraphElement ge1, GraphElement ge2, Point2D p){ BasicStroke fatStroke = new BasicStroke((float) (2 * MAX_DIST)); Line2D line = new Line2D.Double( ge1.getBounds().getCenterX(), ge1.getBounds().getCenterY(), ge2.getBounds().getCenterX(), ge2.getBounds().getCenterY()); Shape fatPath = fatStroke.createStrokedShape(line); return fatPath.contains(p); } /** * Returns a line connecting the centre of the graph elements passed as argument. * * @param start the first graph element * @param end the second graph element * * @return a line connecting {@code start} and {@code end} */ protected Line2D.Double getSegment(GraphElement start, GraphElement end){ Rectangle2D startBounds = start.getBounds(); Rectangle2D endBounds = end.getBounds(); Direction d = new Direction(endBounds.getCenterX() - startBounds.getCenterX(), endBounds.getCenterY() - startBounds.getCenterY()); return new Line2D.Double(start.getConnectionPoint(d), end.getConnectionPoint(d.turn(180))); } /** * Returns a list of the points where this edge and the nodes it connects come to a contact * * @return a list of points */ public List<Point2D> getConnectionPoints(){ List<Point2D> list = new LinkedList<Point2D>(); if(points.isEmpty()){ Line2D line = getSegment(nodes.get(0),nodes.get(1)); list.add(line.getP1()); list.add(line.getP2()); }else{ for(InnerPoint p : points){ for(GraphElement ge : p.neighbours) if(ge instanceof Node){ Direction d = new Direction(p.getBounds().getCenterX() - ge.getBounds().getCenterX(),p.getBounds().getCenterY() - ge.getBounds().getCenterY()); list.add(((Node)ge).getConnectionPoint(d)); } } } return list; } /** * Gets the stipple pattern of this edge line style * * @see LineStyle#getStipplePattern() * * @return an int representing the stipple pattern of this edge */ public int getStipplePattern(){ return getStyle().getStipplePattern(); } /** * Bends one of the lines forming this edge. * * When an line is bent, if the location where the bending happens is a line then a new * inner point is created and the line is broken into two sub lines. If the location is an * already existing inner point, then the point is translated. * * @param p the starting point of the bending * @param source the source of the bending action */ public void bend(Point2D p,Object source) { boolean found = false; if(points.isEmpty()){ newInnerPoint.neighbours.addAll(nodes); points.add(newInnerPoint); newPointCreated = false; }else if(newPointCreated){ /* find the segment closest to where the new point lays */ InnerPoint closestP1 = null; GraphElement closestP2 = null; double minDist = 0; for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){ InnerPoint ePoint = pItr.next(); for(ListIterator<GraphElement> geItr = ePoint.neighbours.listIterator(); geItr.hasNext() && !found;){ /* find the neighbour of the current edge point whose line the new point lays on */ GraphElement next = geItr.next(); double dist = Line2D.ptSegDist( ePoint.getBounds().getCenterX(), ePoint.getBounds().getCenterY(), next.getBounds().getCenterX(), next.getBounds().getCenterY(), downPoint.getX(), downPoint.getY() ); if(closestP1 == null || dist < minDist){ closestP1 = ePoint; closestP2 = next; minDist = dist; continue; } } } if(closestP2 instanceof InnerPoint ){ /* remove current edge point from the neighbour's neighbours */ ((InnerPoint)closestP2).neighbours.remove(closestP1); /* add the new inner point */ ((InnerPoint)closestP2).neighbours.add(newInnerPoint); } /*remove old neighbour from edge inner point neighbours */ closestP1.neighbours.remove(closestP2); newInnerPoint.neighbours.add(closestP1); newInnerPoint.neighbours.add(closestP2); /* add the new node to the list of EdgeNodes of this edge */ points.add(newInnerPoint); closestP1.neighbours.add(newInnerPoint); found = true; newPointCreated = false; } newInnerPoint.translate(p, p.getX() - newInnerPoint.getBounds().getCenterX(), p.getY() - newInnerPoint.getBounds().getCenterY(),DiagramEventSource.NONE); notifyChange(new ElementChangedEvent(this,this,"bend",source)); } /** * Returns the line where the edge name will be painted. If the edge is only made out * of a straight line then this will be returned. * * If the edge has been broken into several segments (by bending it) then the central * line is returned. If the edge connects more than two nodes then a line (not necessarily * matching the edge) that has the central point in its centre is returned. Note that a * edge connecting more than two nodes is painted as a central point connected to all the nodes. * * @return the line where the name will be painted */ public Line2D getNameLine(){ if(points.isEmpty()){ // straight line return getSegment(nodes.get(0),nodes.get(1)); }else{ if(masterInnerPoint != null){/* multiended edge */ Rectangle2D bounds = masterInnerPoint.getBounds(); Point2D p = new Point2D.Double(bounds.getCenterX() - 1,bounds.getCenterY()); Point2D q = new Point2D.Double(bounds.getCenterX() + 1,bounds.getCenterY()); return new Line2D.Double(p, q); }else{ GraphElement ge1 = nodes.get(0); GraphElement ge2 = nodes.get(1); InnerPoint c1 = null; InnerPoint c2 = null; for(InnerPoint innp : points){ if(innp.getNeighbours().contains(ge1)){ c1 = innp; } if(innp.getNeighbours().contains(ge2)){ c2 = innp; } } /* we only have two nodes but the edge has been bended */ while((c1 != c2)&&(!c2.getNeighbours().contains(c1))){ if(c1.getNeighbours().get(0) == ge1){ ge1 = c1; c1 = (InnerPoint)c1.getNeighbours().get(1); } else{ ge1 = c1; c1 = (InnerPoint)c1.getNeighbours().get(0); } if(c2.getNeighbours().get(0) == ge2){ ge2 = c2; c2 = (InnerPoint)c2.getNeighbours().get(1); } else{ ge2 = c2; c2 = (InnerPoint)c2.getNeighbours().get(0); } } Point2D p = new Point2D.Double(); Point2D q = new Point2D.Double(); if(c1 == c2){ Rectangle2D bounds = c1.getBounds(); p.setLocation( bounds.getCenterX() - 1,bounds.getCenterY()); q.setLocation( bounds.getCenterX() + 1,bounds.getCenterY()); }else{ Rectangle2D bounds = c1.getBounds(); p.setLocation( bounds.getCenterX(),bounds.getCenterY()); bounds = c2.getBounds(); q.setLocation(bounds.getCenterX(),bounds.getCenterY()); } return new Line2D.Double(p,q); } } } /** * Encodes all the relevant data of this object in XML format. * * @param doc an XML document * @param parent the parent XML element, where tag about this edge will be inserted * @param nodes a list of all nodes of the diagram */ public void encode(Document doc, Element parent, List<Node> nodes){ parent.setAttribute(PersistenceManager.TYPE,getType()); parent.setAttribute(PersistenceManager.NAME, getName()); parent.setAttribute(PersistenceManager.ID, String.valueOf(getId())); int numNodes = getNodesNum(); if(numNodes > 0){ Element nodesTag = doc.createElement(PersistenceManager.NODES); parent.appendChild(nodesTag); for(int i=0; i<numNodes;i++){ Element nodeTag = doc.createElement(PersistenceManager.NODE); nodeTag.setAttribute(PersistenceManager.ID, String.valueOf(getNodeAt(i).getId())); nodeTag.setAttribute(PersistenceManager.LABEL, getEndLabel(getNodeAt(i))); nodesTag.appendChild(nodeTag); } } if(!points.isEmpty()){ Element pointsTag = doc.createElement(PersistenceManager.POINTS); parent.appendChild(pointsTag); for(InnerPoint point : points){ Element pointTag = doc.createElement(PersistenceManager.POINT); pointsTag.appendChild(pointTag); pointTag.setAttribute(PersistenceManager.ID, String.valueOf(-(points.indexOf(point)+1))); Element positionTag = doc.createElement(PersistenceManager.POSITION); pointTag.appendChild(positionTag); Rectangle2D bounds = point.getBounds(); positionTag.setAttribute(PersistenceManager.X,String.valueOf(bounds.getX())); positionTag.setAttribute(PersistenceManager.Y,String.valueOf(bounds.getY())); Element neighboursTag = doc.createElement(PersistenceManager.NEIGHBOURS); pointTag.appendChild(neighboursTag); StringBuilder builder = new StringBuilder(); for(GraphElement ge : point.getNeighbours()){ if(ge instanceof Node){ builder.append(((Node)ge).getId()); }else{ builder.append(-(points.indexOf(ge)+1)); } builder.append(" "); } builder.deleteCharAt(builder.length()-1); neighboursTag.setTextContent(builder.toString()); } } } /** * Decodes an edge from the XML representation. * * @see #encode(Document, Element, List) * * @param doc an XML document * @param edgeTag the tag in the XML file related to this edge * @param nodesId a map linking node ids in the XML file to the {@code Node} objects they represent * @throws IOException if something goes wrong while reading the XML file */ public void decode(Document doc, Element edgeTag, Map<String,Node> nodesId) throws IOException{ setName(edgeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS); if(getName().isEmpty()) throw new IOException(); try{ setId(Integer.parseInt(edgeTag.getAttribute(PersistenceManager.ID))); }catch(NumberFormatException nfe){ throw new IOException(nfe); } NodeList nodeList = edgeTag.getElementsByTagName(PersistenceManager.NODE); List<DiagramNode> attachedNodes = new ArrayList<DiagramNode>(nodeList.getLength()); List<String> labels = new ArrayList<String>(nodeList.getLength()); for(int i=0; i<nodeList.getLength();i++){ String id = ((Element)nodeList.item(i)).getAttribute(PersistenceManager.ID); if(!nodesId.containsKey(id)) throw new IOException(); attachedNodes.add(nodesId.get(id)); labels.add(((Element)nodeList.item(i)).getAttribute(PersistenceManager.LABEL)); } try { connect(attachedNodes); } catch (ConnectNodesException e) { throw new IOException(e); } for(int i=0; i < labels.size(); i++){ setEndLabel(attachedNodes.get(i), labels.get(i),DiagramEventSource.PERS); } Map<String, InnerPoint> pointsId = new LinkedHashMap<String, InnerPoint>(); NodeList pointTagList = edgeTag.getElementsByTagName(PersistenceManager.POINT); for(int i=0; i<pointTagList.getLength(); i++){ InnerPoint point = new InnerPoint(); Element pointTag = (Element)pointTagList.item(i); String id = pointTag.getAttribute(PersistenceManager.ID); /* id of property nodes must be a negative value */ try{ if(Integer.parseInt(id) >= 0) throw new IOException(); }catch(NumberFormatException nfe){ throw new IOException(nfe); } pointsId.put(id, point); if(pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0) == null) throw new IOException(); Element pointPositionTag = (Element)pointTag.getElementsByTagName(PersistenceManager.POSITION).item(0); double dx = 0,dy = 0; try{ dx = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.X)); dy = Double.parseDouble(pointPositionTag.getAttribute(PersistenceManager.Y)); }catch(NumberFormatException nfe){ throw new IOException(); } point.translate(new Point2D.Double(), dx, dy,DiagramEventSource.PERS); } /* remove the master inner point eventually created by connect */ /* we're going to replace it with the one in the XML file */ points.clear(); /* re do the cycle when all the points id have been Map-ped */ for(int i=0; i<pointTagList.getLength(); i++){ Element pointTag = (Element)pointTagList.item(i); InnerPoint point = pointsId.get(pointTag.getAttribute(PersistenceManager.ID)); if(pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0) == null) throw new IOException(); Element pointNeighboursTag = (Element)pointTag.getElementsByTagName(PersistenceManager.NEIGHBOURS).item(0); String pointNeighboursTagContent = pointNeighboursTag.getTextContent(); String[] neighboursId = pointNeighboursTagContent.split(" "); for(String neighbourId : neighboursId){ GraphElement ge = nodesId.get(neighbourId); if(ge == null) // it ain't a node ge = pointsId.get(neighbourId); if(ge == null) throw new IOException(); point.neighbours.add(ge); } points.add(point); if(i==0) masterInnerPoint = point; } } /** * Returns the minimum number of nodes that edge of this type can connect * * @return the minimum nodes for edges of this type */ public int getMinAttachedNodes(){ return minAttachedNodes; } /** * Returns the maximum number of nodes that edge of this type can connect * * @return the maximum nodes for edges of this type */ public int getMaxAttachedNodes(){ return maxAttachedNodes; } /** * Return the line style of this edge * * @return the line style of this edge */ public LineStyle getStyle(){ return style; } protected Point2D downPoint; private List<Node> nodes; /* list containing the vertex of the edge which are not nodes */ /** * The list of the inner points of this edge */ protected List<InnerPoint> points; /** * For edges connecting more than two nodes, this is the central inner point where * all the lines from the nodes join */ protected InnerPoint masterInnerPoint; private boolean newPointCreated; private InnerPoint newInnerPoint; private int minAttachedNodes; private int maxAttachedNodes; private LineStyle style; private static final double MAX_DIST = 5; private static final Color POINT_COLOR = Color.GRAY; /** * The end description for an end that has no hand description set by the user */ public static final String NO_ENDDESCRIPTION_STRING = ResourceBundle.getBundle(EditorFrame.class.getName()).getString("no_arrow_string"); /** * When an edge's (straight) line is bent it breaks into two sub lines. At the point where this two * sub lines join a square shaped point is painted. This class represent that point. Objects of this class * are graph elements and the user can click on them and translate them along the graph. * */ protected static class InnerPoint implements GraphElement{ /** * Creates a new inner point */ public InnerPoint(){ bounds = new Rectangle2D.Double(0,0,DIM,DIM); neighbours = new LinkedList<GraphElement>(); } @Override public void startMove(Point2D p, Object source){} @Override public void stopMove(Object source){} @Override public void draw(Graphics2D g2){ Color oldColor = g2.getColor(); g2.setColor(POINT_COLOR); g2.fill(bounds); g2.setColor(oldColor); } @Override public boolean contains(Point2D p){ return bounds.contains(p); } @Override public Point2D getConnectionPoint(Direction d){ return new Point2D.Double(bounds.getCenterX(),bounds.getCenterY()); } @Override public void translate(Point2D p, double dx, double dy, Object source){ bounds.setFrame(bounds.getX() + dx, bounds.getY() + dy, bounds.getWidth(), bounds.getHeight()); } @Override public Rectangle2D getBounds(){ return (Rectangle2D)bounds.clone(); } /** * Neighbours are the graph elements (either nodes or other inner points) this inner point is * directly connected to. Directly connected means there is a straight line from this node * and the neighbour. * * @return a list of neighbours of this inner node */ public List<GraphElement> getNeighbours(){ return neighbours; } /** * Returns true if this inner node and {@code ge} are neighbours. * * @see #getNeighbours() * * @param ge the graph element to be tested * @return {@code true} if {@code ge} is a neighbour of this graph element, {@code false} otherwise * */ public boolean hasNeighbour(GraphElement ge){ return neighbours.contains(ge); } @Override public String toString(){ return "EdgePoint: "+bounds.getCenterX()+"-"+bounds.getCenterY(); } private Rectangle2D bounds; private List<GraphElement> neighbours; private static final int DIM = 7; } /** * A representation of this edge as a set of 2D points. * Every node the edge connects and every inner point are represented as a pair with coordinates * of their centre. Furthermore an adjacency matrix holds the information * about which point is connected to which is. This representation of the edge is * used in the haptic space, being more suitable for haptic devices. * */ public static class PointRepresentation { public PointRepresentation(int size){ xs = new double[size]; ys = new double[size]; adjMatrix = new BitSet[size]; for(int i=0; i<size; i++){ adjMatrix[i] = new BitSet(size); } } /** * An array with all the x coordinate of the edge's points (nodes and inner points) */ public double xs[]; /** * An array with all the y coordinate of the edge's points (nodes and inner points) */ public double ys[]; /** * The adjacency matrix. If the i-th bit of {@code adjMatrix[j]} is set to {@code true} * it means there is a direct line connecting the i-th point (coordinates {@code (xs[i],ys[i])} * to the j-th point (coordinates {@code (xs[j],ys[j])}. Note that connection are represented only * once to avoid double paintings by the haptic engine. So if the i-th bit of {@code adjMatrix[j]} * is set to {@code true}, the j-th bit of {@code adjMatrix[i]} and information redundancy is avoided. */ public BitSet adjMatrix[]; /** * The index of the beginning of the nodes (after inner points) in {@code adjMatrix}. * So if the edge has three nodes and two inner points. The inner points * will be at {@code adjMatrix[0]} and {@code adjMatrix[1]} and the nodes at the following indexes. * In this case {@code nodeStart} is equal to 2. */ public int nodeStart; } /** * Returns a new {@code PointRepresentation} of this edge * * @return a new {@code PointRepresentation} of this edge */ public PointRepresentation getPointRepresentation(){ PointRepresentation pr = new PointRepresentation(points.size()+nodes.size()); if(points.isEmpty()){ // two ended edge pr.xs[0] = nodes.get(0).getBounds().getCenterX(); pr.ys[0] = nodes.get(0).getBounds().getCenterY(); pr.xs[1] = nodes.get(1).getBounds().getCenterX(); pr.ys[1] = nodes.get(1).getBounds().getCenterY(); // we only need one edge, else it would be painted twice pr.adjMatrix[0].set(1); pr.nodeStart = 0; }else{ //[ point 1, point 2, point 3, ... , point n, node, 1 node 2, ... , node n ] int pSize = points.size(); pr.nodeStart = pSize; // the first node starts after the points for(int i=0; i<pSize;i++){ pr.xs[i] = points.get(i).getBounds().getCenterX(); pr.ys[i] = points.get(i).getBounds().getCenterY(); for(GraphElement ge : points.get(i).neighbours){ if(ge instanceof InnerPoint) pr.adjMatrix[i].set(points.indexOf(ge)); else //Node pr.adjMatrix[i].set(pSize+nodes.indexOf(ge)); } } /* set the coordinates of the nodes, no adj matrix needed as the inner points are enough */ for(int i=0 ; i<nodes.size(); i++){ pr.xs[pSize+i] = nodes.get(i).getBounds().getCenterX(); pr.ys[pSize+i] = nodes.get(i).getBounds().getCenterY(); } } return pr; } }