package maps.gml.editor; import java.awt.Color; import java.awt.Insets; import java.awt.Point; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import javax.swing.undo.AbstractUndoableEdit; import maps.gml.GMLBuilding; import maps.gml.GMLCoordinates; import maps.gml.GMLEdge; import maps.gml.GMLNode; import maps.gml.GMLRoad; import maps.gml.GMLShape; import maps.gml.GMLSpace; import maps.gml.view.LineOverlay; import maps.gml.view.NodeDecorator; import maps.gml.view.SquareNodeDecorator; import rescuecore2.log.Logger; import rescuecore2.misc.Pair; import rescuecore2.misc.geometry.GeometryTools2D; import rescuecore2.misc.geometry.Point2D; /** A tool for creating edges. */ public class SplitShapeTool extends AbstractTool { private static final Color HIGHLIGHT_COLOUR = Color.BLUE; private static final int HIGHLIGHT_SIZE = 6; private static final double THRESHOLD = 0.001; private Listener listener; private NodeDecorator nodeHighlight; private LineOverlay overlay; private GMLNode hover; private GMLNode start; private GMLNode end; // private GMLEdge edge; /** Construct a CreateEdgeTool. @param editor The editor instance. */ public SplitShapeTool(GMLEditor editor) { super(editor); listener = new Listener(); nodeHighlight = new SquareNodeDecorator(HIGHLIGHT_COLOUR, HIGHLIGHT_SIZE); overlay = new LineOverlay(HIGHLIGHT_COLOUR, true); } @Override public String getName() { return "Split shape"; } @Override public void activate() { editor.getViewer().addMouseListener(listener); editor.getViewer().addMouseMotionListener(listener); editor.getViewer().addOverlay(overlay); hover = null; start = null; end = null; // edge = null; } @Override public void deactivate() { editor.getViewer().removeMouseListener(listener); editor.getViewer().removeMouseMotionListener(listener); editor.getViewer().clearAllNodeDecorators(); editor.getViewer().removeOverlay(overlay); editor.getViewer().repaint(); } private void setHover(GMLNode node) { if (hover == node) { return; } if (hover != null) { editor.getViewer().clearNodeDecorator(hover); } hover = node; if (hover != null) { editor.getViewer().setNodeDecorator(nodeHighlight, hover); } editor.getViewer().repaint(); } private void setStart(GMLNode node) { if (start == node) { return; } if (start != null) { editor.getViewer().clearNodeDecorator(start); } start = node; if (start != null) { editor.getViewer().setNodeDecorator(nodeHighlight, start); } editor.getViewer().repaint(); } private void setEnd(GMLNode node) { if (start == node || end == node) { return; } if (end != null) { editor.getViewer().clearNodeDecorator(end); } end = node; if (end != null) { editor.getViewer().setNodeDecorator(nodeHighlight, end); } editor.getViewer().repaint(); } private class Listener implements MouseListener, MouseMotionListener { @Override public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { Point p = fixEventPoint(e.getPoint()); GMLCoordinates c = editor.getViewer().getCoordinatesAtPoint(p.x, p.y); GMLNode node = editor.getMap().findNearestNode(c.getX(), c.getY()); overlay.setStart(new Point2D(node.getX(), node.getY())); setStart(node); setHover(null); } } @Override public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { if (start != null && end != null) { SplitShapeEdit edit = splitByEdge(); editor.setChanged(); if (edit != null) { editor.addEdit(edit); } editor.getViewer().clearAllNodeDecorators(); overlay.setStart(null); overlay.setEnd(null); editor.getViewer().repaint(); start = null; end = null; hover = null; } } } private SplitShapeEdit splitByEdge() { Collection<GMLShape> add = new ArrayList<GMLShape>(); Collection<GMLShape> delete = new ArrayList<GMLShape>(); GMLEdge edge = editor.getMap().createEdge(start, end); Collection<GMLEdge> startEdges = editor.getMap().getAttachedEdges(start); Collection<GMLEdge> endEdges = editor.getMap().getAttachedEdges(end); Collection<GMLShape> startShapes = new HashSet<GMLShape>(); Collection<GMLShape> endShapes = new HashSet<GMLShape>(); for (GMLEdge next : startEdges) { startShapes.addAll(editor.getMap().getAttachedShapes(next)); } for (GMLEdge next : endEdges) { endShapes.addAll(editor.getMap().getAttachedShapes(next)); } for (GMLShape shape : startShapes) { if (endShapes.contains(shape)) { Pair<GMLShape, GMLShape> split = splitShape(shape, edge); if (split != null) { add.add(split.first()); add.add(split.second()); delete.add(shape); } } } if (!add.isEmpty()) { edge.setPassable(true); return new SplitShapeEdit(edge, add, delete); } else { editor.getMap().remove(edge); return null; } } private Pair<GMLShape, GMLShape> splitShape(GMLShape shape, GMLEdge edge) { List<GMLNode> nodes1 = new ArrayList<GMLNode>(); List<GMLNode> nodes2 = new ArrayList<GMLNode>(); boolean first = true; for (GMLNode n : shape.getUnderlyingNodes()) { if (n == edge.getStart() || n == edge.getEnd()) { first = !first; nodes1.add(n); nodes2.add(n); } else if (first) { nodes1.add(n); } else { nodes2.add(n); } } if (nodes1.size() <= 2 || nodes2.size() <= 2) { return null; } //Check if we really split an interior edge double oldArea = area(shape.getUnderlyingNodes()); double area1 = area(nodes1); double area2 = area(nodes2); if (area1 + area2 > oldArea + THRESHOLD) { return null; } GMLShape s1 = null; GMLShape s2 = null; if (shape instanceof GMLBuilding) { GMLBuilding b = (GMLBuilding) shape; GMLBuilding b1 = editor.getMap().createBuildingFromNodes(nodes1); GMLBuilding b2 = editor.getMap().createBuildingFromNodes(nodes2); b1.setCode(b.getCode()); b2.setCode(b.getCode()); b1.setFloors(b.getFloors()); b2.setFloors(b.getFloors()); b1.setImportance(b.getImportance()); b2.setImportance(b.getImportance()); s1 = b1; s2 = b2; } else if (shape instanceof GMLRoad) { //GMLBuilding b = (GMLBuilding) shape; s1 = editor.getMap().createRoadFromNodes(nodes1); s2 = editor.getMap().createRoadFromNodes(nodes2); } else if (shape instanceof GMLSpace) { //GMLBuilding b = (GMLBuilding) shape; s1 = editor.getMap().createSpaceFromNodes(nodes1); s2 = editor.getMap().createSpaceFromNodes(nodes2); } else { throw new IllegalArgumentException("Shape is not a building, road or space"); } editor.getMap().remove(shape); return new Pair<GMLShape, GMLShape>(s1, s2); } private double area(List<GMLNode> nodes) { List<Point2D> vertices = new ArrayList<Point2D>(); for (GMLNode n : nodes) { vertices.add(new Point2D(n.getX(), n.getY())); } return GeometryTools2D.computeArea(vertices); } @Override public void mouseDragged(MouseEvent e) { if (start != null) { Point p = fixEventPoint(e.getPoint()); GMLCoordinates c = editor.getViewer().getCoordinatesAtPoint(p.x, p.y); GMLNode node = editor.getMap().findNearestNode(c.getX(), c.getY()); overlay.setEnd(new Point2D(node.getX(), node.getY())); setEnd(node); } } @Override public void mouseMoved(MouseEvent e) { Point p = fixEventPoint(e.getPoint()); GMLCoordinates c = editor.snap(editor.getViewer().getCoordinatesAtPoint(p.x, p.y)); GMLNode node = editor.getMap().findNearestNode(c.getX(), c.getY()); setHover(node); } @Override public void mouseClicked(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } private Point fixEventPoint(Point p) { Insets insets = editor.getViewer().getInsets(); return new Point(p.x - insets.left, p.y - insets.top); } } private class SplitShapeEdit extends AbstractUndoableEdit { private Collection<GMLShape> add; private Collection<GMLShape> remove; private GMLEdge edge; public SplitShapeEdit(GMLEdge edge, Collection<GMLShape> add, Collection<GMLShape> remove) { this.edge = edge; this.add = add; this.remove = remove; } @Override public void undo() { super.undo(); editor.getMap().removeEdge(edge); editor.getMap().remove(add); editor.getMap().add(remove); editor.getViewer().repaint(); } @Override public void redo() { super.redo(); editor.getMap().addEdge(edge); for (GMLShape r : remove) { Logger.debug("remove: " + r.toString()); } for (GMLShape r : add) { Logger.debug("add: " + r.toString()); } editor.getMap().remove(remove); editor.getMap().add(add); editor.getViewer().repaint(); } } }