]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Performance and resource consumption optimization for G2D picking 88/2688/2
authorjsimomaa <jani.simomaa@gmail.com>
Fri, 27 Jul 2018 07:14:49 +0000 (10:14 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Tue, 26 Feb 2019 07:56:39 +0000 (07:56 +0000)
* Scene graph R-tree based spatial optimization of picked
  IElement-material resolution
* Parallel processing for both resolving the elements to pick and
  picking the elements
* Added INode -> IElement map management to ElementPainter to have the
  fastest possible means available in picking for getting from scene
  graph nodes to their corresponding IElement instances.

gitlab #66

Change-Id: I524da0c2695cdda2eb4f0a1fa318f48ff8fcff24
(cherry picked from commit 48135dcd03588783f9c1b688aaa53cdaacba6ef2)

18 files changed:
bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java
bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionEditingSupport.java
bundles/org.simantics.diagram/src/org/simantics/diagram/participant/PointerInteractor2.java
bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/impl/PickContextImpl.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementInteractor.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ZOrderHandler.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/BoxSelectionMode.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java
bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java
bundles/org.simantics.g2d/src/org/simantics/g2d/scenegraph/SceneGraphConstants.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/TerminalInformer.java
bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java

index 8ebd1edc56e992841f5ee9955f4ea7fed13eb674..38bdc706cb88e8b20a959b03d18bfac02ec17de9 100644 (file)
@@ -577,6 +577,7 @@ public class ConnectTool2 extends AbstractMode {
             }
         } else {
             RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(
+                    getContext(),
                     diagram,
                     pi.getCanvasPickShape(me.controlPosition),
                     pi.getPickDistance());
index d0307eb161271a5abf1703c243bf7198eede3355..fe11570fe11f770374c845168e19a3ec4b457ce1 100644 (file)
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.simantics.Simantics;
 import org.simantics.db.Resource;
 import org.simantics.db.WriteGraph;
 import org.simantics.db.common.request.WriteRequest;
@@ -54,14 +55,13 @@ import org.simantics.g2d.element.IElement;
 import org.simantics.g2d.element.handler.Children;
 import org.simantics.g2d.participant.TransformUtil;
 import org.simantics.g2d.participant.WorkbenchStatusLine;
-import org.simantics.scenegraph.g2d.events.MouseEvent;
 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
-import org.simantics.ui.SimanticsUI;
 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
 import org.simantics.utils.datastructures.hints.IHintContext.Key;
 import org.simantics.utils.datastructures.hints.IHintListener;
@@ -123,7 +123,7 @@ public class ConnectionEditingSupport extends AbstractDiagramParticipant {
         if (shape == null)
             return false;
 
-        PickRequest req = new PickRequest(shape);
+        PickRequest req = new PickRequest(shape).context(getContext());
         req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
         req.pickFilter = null;
         req.pickSorter = NODES_LAST;
@@ -299,7 +299,7 @@ public class ConnectionEditingSupport extends AbstractDiagramParticipant {
             final AtomicReference<Resource> newBp = new AtomicReference<Resource>();
 
             try {
-                SimanticsUI.getSession().syncRequest(new WriteRequest() {
+                Simantics.getSession().syncRequest(new WriteRequest() {
                     @Override
                     public void perform(WriteGraph graph) throws DatabaseException {
                         DiagramResource DIA = DiagramResource.getInstance(graph);
index 832e9aba98d26176f4c764e8a7a43a6a2ce9d92c..e20d58d5b1d6465262bfb73d4404a2c56fdfa829 100644 (file)
@@ -78,7 +78,8 @@ public class PointerInteractor2 extends PointerInteractor {
 
             if (tis.isEmpty()) {
                 // Look for potential connection to branch.
-                RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(diagram,
+                RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(
+                        getContext(), diagram,
                         getCanvasPickShape(me.controlPosition), getPickDistance());
                 if (cp != null) {
                     bsi = initiateRouteGraphConnectTool(cp, me.mouseId);
index cff52a3d5d88ace604e559b54b591d649a9859e4..e39c73d809120b760110c3476d44368f76e78336 100644 (file)
@@ -842,9 +842,9 @@ public class RouteGraphConnectTool extends AbstractMode {
 
     // ------------------------------------------------------------------------
 
-    static RouteGraphTarget pickRouteGraphConnection(IDiagram diagram, Shape pickShape, double pickDistance) {
+    static RouteGraphTarget pickRouteGraphConnection(ICanvasContext ctx, IDiagram diagram, Shape pickShape, double pickDistance) {
         ArrayList<IElement> elements = new ArrayList<IElement>();
-        PickRequest req = new PickRequest(pickShape);
+        PickRequest req = new PickRequest(pickShape).context(ctx);
         DiagramUtils.pick(diagram, req, elements);
         for (Iterator<IElement> it = elements.iterator(); it.hasNext();) {
             IElement e = it.next();
index 01a3d51581e6e61b702ca180ec6038b4061bb9ef..0c11d348ab62fcc3860437b1e8a72f779f5349d5 100644 (file)
@@ -20,9 +20,13 @@ import java.util.Map;
 import org.simantics.g2d.canvas.ICanvasContext;
 import org.simantics.g2d.canvas.ICanvasParticipant;
 import org.simantics.g2d.connection.IConnectionAdvisor;
+import org.simantics.g2d.diagram.handler.impl.PickContextImpl;
+import org.simantics.g2d.diagram.participant.ElementPainter;
+import org.simantics.g2d.element.IElement;
 import org.simantics.g2d.layers.ILayers;
 import org.simantics.g2d.layers.ILayersEditor;
 import org.simantics.g2d.routing.IRouter2;
+import org.simantics.scenegraph.INode;
 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
 import org.simantics.utils.datastructures.hints.IHintContext.Key;
 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
@@ -186,4 +190,14 @@ public class DiagramHints {
      */
     public static final Key SELECTION_PADDING_SCALE_FACTOR = new KeyOf(Double.class, "SELECTION_PADDING_SCALE_FACTOR");
 
+    /**
+     * An optional cache map that maps scene graph {@link INode}s to
+     * {@link IElement}s to speed up IElement lookups based on SG nodes.
+     * 
+     * @since 1.36.0
+     * @see PickContextImpl
+     * @see ElementPainter
+     */
+    public static final Key NODE_TO_ELEMENT_MAP = new KeyOf(Map.class, "NODE_TO_ELEMENT_MAP");
+
 }
index 8cfea8b59ee46dab4cb2f72f174a338d3d4208e9..7f83cf9bc58ee0131ed79c3e04e93963a9c966f9 100644 (file)
@@ -332,7 +332,7 @@ public class DiagramUtils {
     }
 
     /**
-     * Execute the specified {@link Callback} within a diagram write transaction
+     * Execute the specified <code>callback</code> within a diagram write transaction
      * using the {@link TransactionContext} handler available in the
      * {@link DiagramClass} of the specified {@link Diagram}. The diagram must
      * contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which
index a43e8350a15b2aeff8c2f155558d4cd93b5aa72b..265caa805b604210e12612d49b22a2126ada9c35 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
+import org.simantics.g2d.canvas.ICanvasContext;
 import org.simantics.g2d.connection.handler.ConnectionHandler;
 import org.simantics.g2d.element.IElement;
 import org.simantics.g2d.element.handler.BendsHandler;
@@ -50,6 +51,12 @@ public class PickRequest {
     public PickFilter pickFilter = null;
     public PickSorter pickSorter = null;
 
+    /**
+     * Used to optimize picking if provided via R-tree traversal to find
+     * intersecting elements, not everything.
+     */
+    public ICanvasContext pickContext;
+
     public PickRequest(double x, double y)
     {
         pickArea = new Rectangle2D.Double(x, y, 1, 1);
@@ -67,6 +74,11 @@ public class PickRequest {
         pickArea = GeometryUtils.transformShape(shape, transform);
     }
 
+    public PickRequest context(ICanvasContext ctx) {
+        this.pickContext = ctx;
+        return this;
+    }
+
     public static interface PickFilter {
         boolean accept(IElement e);
 
index 1f6a76903e9247e1c7d8aa3b21da14cc3d833616..a891d62b70a360f7e750097f57cc2e0b26368ef5 100644 (file)
@@ -17,7 +17,13 @@ import java.awt.geom.NoninvertibleTransformException;
 import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import org.simantics.g2d.diagram.DiagramHints;
 import org.simantics.g2d.diagram.IDiagram;
@@ -25,6 +31,7 @@ import org.simantics.g2d.diagram.handler.PickContext;
 import org.simantics.g2d.diagram.handler.PickRequest;
 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
 import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
 import org.simantics.g2d.element.ElementUtils;
 import org.simantics.g2d.element.IElement;
 import org.simantics.g2d.element.handler.ElementLayers;
@@ -34,7 +41,13 @@ import org.simantics.g2d.element.handler.Pick;
 import org.simantics.g2d.element.handler.Pick2;
 import org.simantics.g2d.element.handler.Transform;
 import org.simantics.g2d.layers.ILayers;
+import org.simantics.g2d.scenegraph.SceneGraphConstants;
 import org.simantics.g2d.utils.GeometryUtils;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * @author Toni Kalajainen
@@ -43,6 +56,12 @@ public class PickContextImpl implements PickContext {
 
        public static final PickContextImpl INSTANCE = new PickContextImpl(); 
 
+       private static final Logger LOGGER = LoggerFactory.getLogger(PickContextImpl.class);
+
+       private static final boolean PERF = false;
+
+       private static final ThreadLocal<Rectangle2D> perThreadElementBounds = ThreadLocal.withInitial(() -> new Rectangle2D.Double());
+
        @Override
        public void pick(
                        IDiagram diagram, 
@@ -53,163 +72,312 @@ public class PickContextImpl implements PickContext {
                assert(request!=null);
                assert(finalResult!=null);
 
-               ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
+               long startTime = PERF ? System.nanoTime() : 0L;
+
+               List<IElement> result = pickElements(diagram, request);
 
-               Collection<IElement> result = finalResult;
-               if (request.pickSorter != null) {
-                       // Need a temporary List<IElement> for PickSorter
-                       result = new ArrayList<IElement>();
+               if (PERF) {
+                       long endTime = System.nanoTime();
+                       LOGGER.info("[picked " + result.size() + " elements @ " + request.pickArea + "] total pick time : " + ((endTime - startTime)*1e-6));
                }
-               Rectangle2D elementBounds = new Rectangle2D.Double();
-               nextElement:
-               for (IElement e : diagram.getSnapshot())
-               {
+
+               if (!result.isEmpty()) {
+                       if (request.pickSorter != null) {
+                               List<IElement> elems = new ArrayList<>(result);
+                               request.pickSorter.sort(elems);
+                               finalResult.addAll(elems);
+                       } else {
+                               finalResult.addAll(result);
+                       }
+               }
+       }
+
+       private static class PickFilter implements Predicate<IElement> {
+
+               private final PickRequest request;
+               private final ILayers layers;
+               private final boolean checkLayers;
+
+               public PickFilter(PickRequest request, ILayers layers) {
+                       this.request = request;
+                       this.layers = layers;
+                       this.checkLayers = layers != null && !layers.getIgnoreFocusSettings();
+               }
+
+               @Override
+               public boolean test(IElement e) {
                        // Ignore hidden elements.
                        if (ElementUtils.isHidden(e))
-                               continue;
+                               return false;
 
                        ElementClass ec = e.getElementClass();
-                       
-                       if(layers != null && !layers.getIgnoreFocusSettings()) {
+
+                       if (checkLayers) {
                                ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
-                               if(el != null) {
-                                       if(!el.isFocusable(e, layers)) continue;
-                               }
-                       }
-                       
-                       if (request.pickFilter!=null && !request.pickFilter.accept(e)) continue;
-                       
-                       Transform t = e.getElementClass().getSingleItem(Transform.class);
-                       AffineTransform canvasToElement = t.getTransform(e);
-                       if (canvasToElement==null) continue;
-                       double det = canvasToElement.getDeterminant();
-                       if (det == 0) {
-                               // Singular transform, just reset the rotation/scaling part to
-                               // allow picking to proceed.
-                               // TODO: this may modify the internal transform value of an element which is not intended
-                               canvasToElement.setToTranslation(
-                                               canvasToElement.getTranslateX(),
-                                               canvasToElement.getTranslateY());
+                               if (el != null && !el.isFocusable(e, layers))
+                                       return false;
                        }
 
-                       // Get bounds, ignore elements that have no bounds
-                       InternalSize b = e.getElementClass().getAtMostOneItemOfClass(InternalSize.class);
-                       if (b==null) continue;
-                       elementBounds.setFrame(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
-                       b.getBounds(e, elementBounds);
-                       if (Double.isNaN(elementBounds.getWidth()) || Double.isNaN(elementBounds.getHeight()))
-                               continue;
-
-                       Shape elementBoundsOnCanvas = GeometryUtils.transformShape(elementBounds, canvasToElement);
-                       if (elementBoundsOnCanvas instanceof Rectangle2D)
-                               elementBoundsOnCanvas = elementBoundsOnCanvas.getBounds2D();
-                       elementBoundsOnCanvas = elementBoundsOnCanvas.getBounds2D();
-                       org.simantics.scenegraph.utils.GeometryUtils.expandRectangle((Rectangle2D)elementBoundsOnCanvas, 1e-3, 1e-3, 1e-3, 1e-3);
-                       
+                       if (request.pickFilter != null && !request.pickFilter.accept(e))
+                               return false;
+
+                       // Get InternalSize for retrieving bounds, ignore elements that have no bounds
+                       InternalSize b = ec.getAtMostOneItemOfClass(InternalSize.class);
+                       if (b == null)
+                               return false;
+
+                       // Anything that is without a transformation is not pickable
+                       AffineTransform canvasToElement = getTransform(e);
+                       if (canvasToElement == null)
+                               return false;
+
+                       Rectangle2D elementBoundsOnCanvas = getElementBounds(e, b, canvasToElement);
+                       if (elementBoundsOnCanvas == null)
+                               return false;
+
                        // Pick with pick handler(s)
-                       List<Pick> pickHandlers = e.getElementClass().getItemsByClass(Pick.class);
-                       if (!pickHandlers.isEmpty())
-                       {
-                           // Rough filtering with bounds
+                       List<Pick> pickHandlers = ec.getItemsByClass(Pick.class);
+                       if (!pickHandlers.isEmpty()) {
+                               // Rough filtering with bounds
                                if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas)) {
-//                                     System.out.println("Element bounds discards " + e.getElementClass());
-                                       continue;
+                                       // System.out.println("Element bounds discards " + e.getElementClass());
+                                       return false;
                                }
-                               
-                               // Convert pick shape to element coordinates
-//                             AffineTransform elementToCanvas;
-//                             try {
-//                                     elementToCanvas = canvasToElement.createInverse();
-//                             } catch (NoninvertibleTransformException e1) {
-//                                     throw new RuntimeException(e1);
-//                             }
-//                             Shape pickShapeInElementCoords = GeometryUtils.transformShape(request.pickArea, elementToCanvas);
-                               for (Pick p : pickHandlers)
-                               {
-                                       if (p instanceof Pick2) {
-                                               Pick2 p2 = (Pick2) p;
-                                               //if (p2.pick(e, pickShapeInElementCoords, request.pickPolicy, result) > 0)
-                                               if (p2.pick(e, request.pickArea, request.pickPolicy, result) > 0)
-                                                       continue nextElement;
-                                       } else {
-                                               //if (p.pickTest(e, pickShapeInElementCoords, request.pickPolicy)) {
-                                               if (p.pickTest(e, request.pickArea, request.pickPolicy)) {
-                                                       result.add(e);
-                                                       continue nextElement;
-                                               }
+
+                               // NOTE: this doesn't support Pick2 interface anymore
+                               for (Pick p : pickHandlers) {
+                                       if (p.pickTest(e, request.pickArea, request.pickPolicy)) {
+                                               return true;
                                        }
                                }
-                               continue nextElement;
+                               return false;
                        }
-                       
-                       // Pick with shape handler(s) 
-                       List<Outline> shapeHandlers = e.getElementClass().getItemsByClass(Outline.class);
-                       if (!shapeHandlers.isEmpty())
-                       {
-                               // Rough filtering with bounds
-                               if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas)) continue;
-                               
-                               // Convert pick shape to element coordinates
-                               AffineTransform elementToCanvas;
-                               try {
-                                       elementToCanvas = canvasToElement.createInverse();
-                               } catch (NoninvertibleTransformException e1) {
-                                       throw new RuntimeException(e1);
+
+                       // Pick with Outline handler(s)
+                       List<Outline> outlineHandlers = ec.getItemsByClass(Outline.class);
+                       if (!outlineHandlers.isEmpty())
+                               return pickByOutline(e, request, elementBoundsOnCanvas, canvasToElement, outlineHandlers);
+
+                       // Finally, pick by rectangle
+                       return pickByBounds(e, request, elementBoundsOnCanvas);
+               }
+
+       }
+
+       private static Rectangle2D toBoundingRectangle(Shape shape) {
+               if (shape instanceof Rectangle2D)
+                       return (Rectangle2D) shape;
+               else
+                       return shape.getBounds2D();
+       }
+
+       private static List<IElement> pickElements(IDiagram diagram, PickRequest request) {
+               ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
+
+               // Get the scene graph nodes that intersect the pick-requests pick shape
+               INode spatialRoot = request.pickContext != null
+                               ? request.pickContext.getSceneGraph().lookupNode(SceneGraphConstants.SPATIAL_ROOT_NODE_ID)
+                               : null;
+
+               if (spatialRoot instanceof RTreeNode) {
+                       // Optimized picking version that no longer supports Pick2 interface
+                       // and therefore doesn't support connections modelled as
+                       // branchpoint/edge subelements of connection elements. 
+
+                       RTreeNode rtree = (RTreeNode) spatialRoot;
+                       Map<INode, IElement> nodeToElement = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+                       if (nodeToElement != null) {
+                               // The most optimized version
+                               return rtree.intersectingNodes(toBoundingRectangle(request.pickArea), new ArrayList<>())
+                                               .stream()
+                                               .parallel()
+                                               .map(nodeToElement::get)
+                                               .filter(Objects::nonNull)
+                                               .filter(new PickFilter(request, layers))
+                                               .collect(Collectors.toList());
+                       } else {
+                               // Slower version for when DiagramHints.NODE_TO_ELEMENT_MAP is not used
+                               Set<IG2DNode> nodes =
+                                               new HashSet<>(
+                                                               rtree.intersectingNodes(
+                                                                               toBoundingRectangle(request.pickArea),
+                                                                               new ArrayList<>())
+                                                               );
+
+                               return diagram.getSnapshot().stream()
+                                               .parallel()
+                                               // Choose only elements that are under the pick region bounds-wise
+                                               .filter(e -> {
+                                                       INode node = e.getHint(ElementHints.KEY_SG_NODE);
+                                                       return nodes.contains(node);
+                                               })
+                                               // Perform comprehensive picking
+                                               .filter(new PickFilter(request, layers))
+                                               .collect(Collectors.toList());
+                       }
+
+               } else {
+
+                       // Fall-back logic that ends up processing everything.
+                       // This still supports all the old logic and the Pick2
+                       // interface in element classes.
+                       List<IElement> result = new ArrayList<>();
+                       boolean checkLayers = layers != null && !layers.getIgnoreFocusSettings();
+
+                       // Do not do this in parallel mode to keep results in proper Z order
+                       diagram.getSnapshot().stream().forEachOrdered(e -> {
+                               // Ignore hidden elements.
+                               if (ElementUtils.isHidden(e))
+                                       return;
+
+                               ElementClass ec = e.getElementClass();
+
+                               if (checkLayers) {
+                                       ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
+                                       if (el != null && !el.isFocusable(e, layers))
+                                               return;
                                }
-                               Shape pickShapeInElementCoords = GeometryUtils.transformShape(request.pickArea, elementToCanvas);
-                               
-                               // Intersection with one shape is enough
-                               if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
-                               {
-                                       for (Outline es : shapeHandlers)
-                                       {
-                                               Shape elementShape = es.getElementShape(e);             
-                                               if (elementShape==null) continue nextElement;
-                                               if (GeometryUtils.intersects(pickShapeInElementCoords, elementShape))
-                                               {
-                                                       result.add(e);
-                                                       continue nextElement;
+
+                               if (request.pickFilter != null && !request.pickFilter.accept(e))
+                                       return;
+
+                               // Get InternalSize for retrieving bounds, ignore elements that have no bounds
+                               InternalSize b = ec.getAtMostOneItemOfClass(InternalSize.class);
+                               if (b == null)
+                                       return;
+
+                               // Anything that is without a transformation is not pickable
+                               AffineTransform canvasToElement = getTransform(e);
+                               if (canvasToElement == null)
+                                       return;
+
+                               Rectangle2D elementBoundsOnCanvas = getElementBounds(e, b, canvasToElement);
+                               if (elementBoundsOnCanvas == null)
+                                       return;
+
+                               // Pick with pick handler(s)
+                               List<Pick> pickHandlers = ec.getItemsByClass(Pick.class);
+                               if (!pickHandlers.isEmpty()) {
+                                       // Rough filtering with bounds
+                                       if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas)) {
+                                               // System.out.println("Element bounds discards " + e.getElementClass());
+                                               return;
+                                       }
+
+                                       for (Pick p : pickHandlers) {
+                                               if (p instanceof Pick2) {
+                                                       Pick2 p2 = (Pick2) p;
+                                                       if (p2.pick(e, request.pickArea, request.pickPolicy, result) > 0)
+                                                               return;
+                                               } else {
+                                                       if (p.pickTest(e, request.pickArea, request.pickPolicy)) {
+                                                               result.add(e);
+                                                               return;
+                                                       }
                                                }
                                        }
-                                       continue nextElement;
+                                       return;
                                }
-                               
-                               // Contains of all shapes is required 
-                               if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
-                               {
-                                       for (Outline es : shapeHandlers)
-                                       {
-                                               Shape elementShape = es.getElementShape(e);
-                                               if (!GeometryUtils.contains(pickShapeInElementCoords, elementShape))
-                                                       continue nextElement;
+
+                               // Pick with shape handler(s)
+                               List<Outline> outlineHandlers = ec.getItemsByClass(Outline.class);
+                               if (!outlineHandlers.isEmpty()) {
+                                       if (pickByOutline(e, request, elementBoundsOnCanvas, canvasToElement, outlineHandlers)) {
+                                               result.add(e);
                                        }
-                                       result.add(e);          
-                                       continue nextElement;
+                                       return;
                                }
-                               continue nextElement;
-                       } 
-                       
-                       // Pick by rectangle
-                       if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
-                       {
-                               if (GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas)) 
+
+                               // Finally, pick by bounding rectangle
+                               if (pickByBounds(e, request, elementBoundsOnCanvas))
                                        result.add(e);
+                       });
+
+                       return result;
+
+               }
+       }
+
+       private static AffineTransform getTransform(IElement e) {
+               // Anything that is without a transformation is not pickable
+               Transform t = e.getElementClass().getSingleItem(Transform.class);
+               AffineTransform canvasToElement = t.getTransform(e);
+               if (canvasToElement == null)
+                       return null;
+
+               double det = canvasToElement.getDeterminant();
+               if (det == 0) {
+                       // Singular transform, only take the translation from it to move on.
+                       canvasToElement = AffineTransform.getTranslateInstance(canvasToElement.getTranslateX(), canvasToElement.getTranslateY());
+               }
+
+               return canvasToElement;
+       }
+
+       private static Rectangle2D getElementBounds(IElement e, InternalSize b, AffineTransform transform) {
+               Rectangle2D elementBounds = perThreadElementBounds.get();
+               elementBounds.setFrame(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+               b.getBounds(e, elementBounds);
+               if (Double.isNaN(elementBounds.getWidth()) || Double.isNaN(elementBounds.getHeight()))
+                       return null;
+
+               // Get optionally transformed axis-aligned bounds of the element,
+               // expanded by 1mm in each direction.
+               Rectangle2D transformedBounds = transform != null
+                               ? toBoundingRectangle(GeometryUtils.transformShape(elementBounds, transform))
+                               : elementBounds;
+               return org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(transformedBounds, 1e-3);
+       }
+
+       private static boolean pickByOutline(IElement e, PickRequest request, Rectangle2D elementBoundsOnCanvas, AffineTransform canvasToElement, List<Outline> outlineHandlers) {
+               // Rough filtering with bounds
+               if (!GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas))
+                       return false;
+
+               // Convert pick shape to element coordinates
+               AffineTransform elementToCanvas;
+               try {
+                       elementToCanvas = canvasToElement.createInverse();
+               } catch (NoninvertibleTransformException ex) {
+                       throw new RuntimeException(ex);
+               }
+               Shape pickShapeInElementCoords = GeometryUtils.transformShape(request.pickArea, elementToCanvas);
+
+               // Intersection with one shape is enough
+               if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) {
+                       for (Outline es : outlineHandlers) {
+                               Shape elementShape = es.getElementShape(e);
+                               if (elementShape == null)
+                                       return false;
+                               if (GeometryUtils.intersects(pickShapeInElementCoords, elementShape)) {
+                                       return true;
+                               }
                        }
-                       
-                       else
-                               
-                       if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
-                       {
-                               if (GeometryUtils.contains(request.pickArea, elementBoundsOnCanvas)) 
-                                       result.add(e);
+                       return false;
+               }
+
+               // Contains of all shapes is required
+               if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) {
+                       for (Outline es : outlineHandlers) {
+                               Shape elementShape = es.getElementShape(e);
+                               if (!GeometryUtils.contains(pickShapeInElementCoords, elementShape))
+                                       return false;
                        }
-                       
+                       return true;
                }
 
-               if (request.pickSorter != null) {
-                       request.pickSorter.sort((List<IElement>) result);
-                       finalResult.addAll(result);
+               return false;
+       }
+
+       private static boolean pickByBounds(IElement e, PickRequest request, Rectangle2D elementBoundsOnCanvas) {
+               if (request.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) {
+                       if (GeometryUtils.intersects(request.pickArea, elementBoundsOnCanvas))
+                               return true;
+               } else if (request.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) {
+                       if (GeometryUtils.contains(request.pickArea, elementBoundsOnCanvas))
+                               return true;
                }
+               return false;
        }
 
 }
index caad1ef4b166ae3fc72c87ba7b65b238646eddb9..ae9c1ce07a64da56e7c170149ea23d1261b00e9b 100644 (file)
@@ -148,7 +148,7 @@ public class ElementInteractor extends AbstractDiagramParticipant {
             // Pick element under the mouse
             Point2D controlPos = me.controlPosition;
             Point2D diagramPos = util.controlToCanvas(controlPos, null);
-            PickRequest req = new PickRequest(diagramPos);
+            PickRequest req = new PickRequest(diagramPos).context(getContext());
             req.pickSorter = pickSorter;
             //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;
             ArrayList<IElement> result = new ArrayList<IElement>();
index ba575c630ffb1aff08024fe8e482d2f5423c8116..6e9ff85b1f62c9fb75f4552e346c956cad80113a 100644 (file)
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * Copyright (c) 2007, 2018 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
@@ -8,6 +8,7 @@
  *
  * Contributors:
  *     VTT Technical Research Centre of Finland - initial API and implementation
+ *     Semantum Oy - GitLab issue #66
  *******************************************************************************/
 package org.simantics.g2d.diagram.participant;
 
@@ -141,6 +142,7 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
     }
 
     private static final boolean DEBUG                  = false;
+    private static final boolean NODE_TO_ELEMENT_MAPPING = true;
 
     public static final int      ELEMENT_PAINT_PRIORITY = 10;
 
@@ -198,8 +200,9 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
             return;
 
         if (oldValue != null) {
+            Map<INode, IElement> nodeToElementMap = oldValue.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
             for (IElement e : oldValue.getElements()) {
-                removeElement(e);
+                removeElement(oldValue, e, nodeToElementMap);
             }
 
             oldValue.removeCompositionListener(this);
@@ -217,8 +220,13 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
         }
 
         if (newValue != null) {
+            diagram.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+            Map<INode, IElement> nodeElementMap = NODE_TO_ELEMENT_MAPPING ? new HashMap<>() : null;
+            if (nodeElementMap != null)
+                diagram.setHint(DiagramHints.NODE_TO_ELEMENT_MAP, nodeElementMap);
+
             for (IElement e : newValue.getElements()) {
-                addElement(e, false);
+                addElement(newValue, e, false, nodeElementMap);
             }
 
             newValue.addCompositionListener(this);
@@ -242,7 +250,8 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
     public void initSG(G2DParentNode parent) {
         diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);
         diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);
-        elementParent = diagramParent.addNode("spatialRoot", RTreeNode.class);
+        elementParent = diagramParent.addNode(SceneGraphConstants.SPATIAL_ROOT_NODE_NAME, RTreeNode.class);
+        elementParent.setLookupId(SceneGraphConstants.SPATIAL_ROOT_NODE_ID);
         elementParent.setZIndex(0);
     }
 
@@ -499,10 +508,11 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
         if (DEBUG)
             System.out.println("EP.onElementAdded(" + d + ", " + e + ")");
 
+        Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
         if (inDiagramTransaction()) {
-            addElement(e, false);
+            addElement(d, e, false, nodeElementMap);
         } else {
-            addElement(e, true);
+            addElement(d, e, true, nodeElementMap);
         }
     }
     @Override
@@ -510,7 +520,7 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
         if (DEBUG)
             System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");
 
-        removeElement(e);
+        removeElement(d, e, diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP));
     }
 
     @Override
@@ -518,17 +528,19 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
         if (DEBUG)
             System.out.println("EP.elementChildrenChanged: " + event);
 
+        Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+
         for (IElement removed : event.removed) {
-            removeElement(removed);
+            removeElement(diagram, removed, nodeElementMap);
         }
         for (IElement added : event.added) {
-            addElement(added, false);
+            addElement(diagram, added, false, nodeElementMap);
         }
     }
 
     private final List<IElement> childrenTemp = new ArrayList<IElement>();
 
-    public void addElement(IElement e, boolean synchronizeSceneGraphNow) {
+    public void addElement(IDiagram d, IElement e, boolean synchronizeSceneGraphNow, Map<INode, IElement> nodeElementMap) {
         if (DEBUG)
             System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");
 
@@ -563,6 +575,8 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
                 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
                 e.setHint(sgKey, holder);
                 holder.setZIndex(parentNode.getNodeCount() + 1);
+                if (nodeElementMap != null)
+                    nodeElementMap.put(holder, e);
             }
 
         } else {
@@ -575,6 +589,8 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
                 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
                 e.setHint(sgKey, holder);
                 holder.setZIndex(parentNode.getNodeCount() + 1);
+                if (nodeElementMap != null)
+                    nodeElementMap.put(holder, e);
             }
 
         }
@@ -587,7 +603,7 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
             children.getChildren(e, childrenTemp);
             //System.out.println("children: " + childrenTemp);
             for (IElement child : childrenTemp) {
-                addElement(child, false);
+                addElement(d, child, false, nodeElementMap);
             }
             childrenTemp.clear();
         }
@@ -598,7 +614,7 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
         //setTreeDirty();
     }
 
-    protected void removeElement(IElement e) {
+    protected void removeElement(IDiagram d, IElement e, Map<INode, IElement> nodeElementMap) {
         if (DEBUG)
             System.out.println("EP.removeElement(" + e + ")");
 
@@ -624,6 +640,8 @@ public class ElementPainter extends AbstractDiagramParticipant implements Compos
             Node n = e.removeHint(sgKey);
             if (n != null) {
                 n.remove();
+                if (nodeElementMap != null)
+                    nodeElementMap.remove(n);
             }
         }
 
index 39baa912c6cfc99724aaa09b6c40a57b543af020..d9c707993798974e4bb5ffce2fef813fce57f5df 100644 (file)
@@ -189,7 +189,7 @@ public class TerminalPainter extends AbstractDiagramParticipant {
 
             // Paint terminals normally
             if (paintAreaTerminals || paintPointTerminals) {
-                List<TerminalInfo> pickedTerminals = TerminalUtil.pickTerminals(diagram, null, paintPointTerminals, paintAreaTerminals);
+                List<TerminalInfo> pickedTerminals = TerminalUtil.pickTerminals(getContext(), diagram, null, paintPointTerminals, paintAreaTerminals);
                 paintTerminals(node, Color.BLUE, diagram, null, pickedTerminals, null);
                 if(pickedTerminals.size() > 0) repaint = true;
             }
@@ -208,7 +208,7 @@ public class TerminalPainter extends AbstractDiagramParticipant {
                        Rectangle2D controlPickRect = getPickRectangle(mi.controlPosition.getX(), mi.controlPosition.getY());
                     Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, invTx);
 
-                    List<TerminalInfo> tis = TerminalUtil.pickTerminals(diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals);
+                    List<TerminalInfo> tis = TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals);
                     paintTerminals(node, Color.RED, diagram, canvasPickRect.getBounds2D(), tis, strategy);
                     if(tis.size() > 0) repaint = true;
                 }
index e393b7c13f391680fb9c3ee493a5a0ed2961e76a..007d039f23715e4ec65446739c8cc941cbe80cd5 100644 (file)
@@ -48,7 +48,7 @@ public class ZOrderHandler extends AbstractDiagramParticipant {
     @Dependency Selection sel;
     @Dependency PickContext pickContext;
 
-    private final ListenerList zOrderListeners = new ListenerList(ListenerList.IDENTITY);
+    private final ListenerList<ZOrderListener> zOrderListeners = new ListenerList<>(ListenerList.IDENTITY);
 
     public void addOrderListener(ZOrderListener listener) {
         zOrderListeners.add(listener);
@@ -70,7 +70,7 @@ public class ZOrderHandler extends AbstractDiagramParticipant {
             Shape area = ElementUtils.getElementShapesOnDiagram(selectedElements);
             if (area==null) return true;
             final ArrayList<IElement> pickedElements = new ArrayList<IElement>();
-            PickRequest req = new PickRequest(area);
+            PickRequest req = new PickRequest(area).context(getContext());
             req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
             pickContext.pick(diagram, req, pickedElements);
             DiagramUtils.inDiagramTransaction(diagram, TransactionType.WRITE, new Runnable() {
@@ -105,7 +105,7 @@ public class ZOrderHandler extends AbstractDiagramParticipant {
             Shape area = ElementUtils.getElementShapesOnDiagram(selectedElements);
             if (area==null) return true;
             final ArrayList<IElement> pickedElements = new ArrayList<IElement>();
-            PickRequest req = new PickRequest(area);
+            PickRequest req = new PickRequest(area).context(getContext());
             req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
             pickContext.pick(diagram, req, pickedElements);
             DiagramUtils.inDiagramTransaction(diagram, TransactionType.WRITE, new Runnable() {
index 6a1d27fd22863b2c04bad1cf34d42dd8ba367ec4..d66b8fbbe92c7a8c47ba58f0f16a78cc0ed2a071 100644 (file)
@@ -84,7 +84,7 @@ public class BoxSelectionMode extends AbstractMode {
                 boolean accumulate = (modifiers & MouseEvent.SHIFT_MASK) != 0;
 
                 Set<IElement> boxSelection = new HashSet<IElement>();
-                PickRequest request = new PickRequest(rect);
+                PickRequest request = new PickRequest(rect).context(getContext());
                 request.pickPolicy = boxSelectMode;
                 pickContext.pick(diagram, request, boxSelection);
 
index fc0f35cb0b2900a58c728812a3ae8b3d89b7d1a2..4f9df238f4b602ec6e95798570ad2cbbb154f04a 100644 (file)
@@ -307,7 +307,7 @@ public class PointerInteractor extends AbstractDiagramParticipant {
         Shape canvasPickRect = getCanvasPickShape(controlPos);
         if (canvasPickRect == null)
             return Collections.emptyList();
-        return TerminalUtil.pickTerminals(diagram, canvasPickRect, true, true);
+        return TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, true, true);
     }
 
     public TerminalInfo pickTerminal(Point2D controlPos)
@@ -315,7 +315,7 @@ public class PointerInteractor extends AbstractDiagramParticipant {
         Shape canvasPickRect = getCanvasPickShape(controlPos);
         if (canvasPickRect == null)
             return null;
-        TerminalInfo ti = TerminalUtil.pickTerminal(diagram, canvasPickRect);
+        TerminalInfo ti = TerminalUtil.pickTerminal(getContext(), diagram, canvasPickRect);
         return ti;
     }
 
@@ -421,7 +421,7 @@ public class PointerInteractor extends AbstractDiagramParticipant {
         Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
         int selectionId = me.mouseId;
 
-        PickRequest req = new PickRequest(canvasPickRect);
+        PickRequest req = new PickRequest(canvasPickRect).context(getContext());
         req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
         req.pickSorter = pickSorter;
         //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;
@@ -592,7 +592,7 @@ public class PointerInteractor extends AbstractDiagramParticipant {
         Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
         int         selectionId     = me.mouseId;
 
-        PickRequest req             = new PickRequest(canvasPickRect);
+        PickRequest req             = new PickRequest(canvasPickRect).context(getContext());
         req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
         List<IElement> pick         = new ArrayList<IElement>();
         pickContext.pick(diagram, req, pick);
@@ -641,7 +641,7 @@ public class PointerInteractor extends AbstractDiagramParticipant {
         assertDependencies();
 
         Point2D         curCanvasPos    = util.controlToCanvas(me.controlPosition, curCanvasDragPos);
-        PickRequest     req             = new PickRequest(me.startCanvasPos);
+        PickRequest     req             = new PickRequest(me.startCanvasPos).context(getContext());
         req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
         List<IElement>  picks           = new ArrayList<IElement>();
         pickContext.pick(diagram, req, picks);
@@ -652,7 +652,7 @@ public class PointerInteractor extends AbstractDiagramParticipant {
                double pickDist = getPickDistance();
             Rectangle2D     controlPickRect     = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);
             Shape           canvasPickRect      = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
-            req = new PickRequest(canvasPickRect);
+            req = new PickRequest(canvasPickRect).context(getContext());
             req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
             pickContext.pick(diagram, req, picks);
             //System.out.println("2nd try: " + picks);
index 677c97f0f4fa0062779d9688ea718e39218f4508..da98294284aba29e314ee056168004c91d92189b 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
+import org.simantics.g2d.canvas.ICanvasContext;
 import org.simantics.g2d.diagram.DiagramUtils;
 import org.simantics.g2d.diagram.IDiagram;
 import org.simantics.g2d.diagram.handler.PickRequest;
@@ -118,7 +119,7 @@ public class TerminalUtil {
      * @param pickAreaTerminals pick terminals that have a shape
      * @return terminals in z-order (bottom to top)
      */
-    public static List<TerminalInfo> pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
+    public static List<TerminalInfo> pickTerminals(ICanvasContext ctx, IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
     {
         boolean clearElements = false;
         List<IElement> elements = null;
@@ -127,7 +128,7 @@ public class TerminalUtil {
             elements = ELEMENTS.get();
             elements.clear();
             clearElements = true;
-            PickRequest req = new PickRequest(pickShape);
+            PickRequest req = new PickRequest(pickShape).context(ctx);
             DiagramUtils.pick(d, req, elements);
         } else {
             // Select all terminals
@@ -201,11 +202,11 @@ public class TerminalUtil {
      * @param pickShape pick area (in diagram coordinate system)
      * @return terminals in z-order (bottom to top)
      */
-    public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)
+    public static TerminalInfo pickTerminal(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
     {
         ArrayList<IElement> elements = ELEMENTS.get();
         elements.clear();
-        PickRequest req = new PickRequest(pickShape);
+        PickRequest req = new PickRequest(pickShape).context(ctx);
         DiagramUtils.pick(diagram, req, elements);
         if (elements.isEmpty())
             return null;
@@ -363,7 +364,7 @@ public class TerminalUtil {
      * @param pickShape
      * @return bends or null
      */
-    public BendsInfo pickBends(IDiagram diagram, Shape pickShape)
+    public BendsInfo pickBends(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
     {
         BendsInfo              result = null;
         double                         bestShortestDist = Double.MAX_VALUE;
@@ -372,7 +373,7 @@ public class TerminalUtil {
 
         ArrayList<IElement> elements = ELEMENTS.get();
         elements.clear();
-        PickRequest req = new PickRequest(pickShape);
+        PickRequest req = new PickRequest(pickShape).context(ctx);
         DiagramUtils.pick(diagram, req, elements);
 
         ArrayList<Bend> bends = new ArrayList<>();
index 6d975b1000b4830c540243d7d4b41102f508ab44..2c082353e8074b4e4132e1ac2e742da28139ec3a 100644 (file)
@@ -42,4 +42,8 @@ public final class SceneGraphConstants {
 
     public static final String[] SELECTIONS_NODE_PATH      = { DATA_NODE_NAME, SELECTIONS_NODE_NAME };
 
+    public static final String   SPATIAL_ROOT_NODE_NAME    = "spatialRoot";
+
+    public static final String   SPATIAL_ROOT_NODE_ID      = SPATIAL_ROOT_NODE_NAME;
+
 }
index c9699fe7727c81ebb8ae81545320f88a4f57a25e..8a4944200dcf5bfa244749496bc0f513fd4dc783 100644 (file)
@@ -172,7 +172,7 @@ public class TerminalInformer extends AbstractDiagramParticipant {
     }
 
     private void pickRect(Rectangle2D rect) {
-        TerminalInfo terminal = TerminalUtil.pickTerminal(diagram, rect);
+        TerminalInfo terminal = TerminalUtil.pickTerminal(getContext(), diagram, rect);
         if (terminal != null) {
             createMessage(terminal, message -> setMessage(null, message));
         } else {
index d9b34dee60455bdf819e03d078cf89a4d1c429d4..52403edd02ea251522270f43ec9904614cda8c0b 100644 (file)
@@ -11,9 +11,6 @@
  *******************************************************************************/
 package org.simantics.scenegraph.g2d.nodes.spatial;
 
-import gnu.trove.TIntObjectHashMap;
-import gnu.trove.TIntProcedure;
-
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.Shape;
@@ -22,6 +19,7 @@ import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Properties;
 import java.util.Set;
 
@@ -37,6 +35,9 @@ import org.simantics.scenegraph.utils.NodeUtil;
 import com.infomatiq.jsi.Rectangle;
 import com.infomatiq.jsi.rtree.RTree;
 
+import gnu.trove.TIntObjectHashMap;
+import gnu.trove.TIntProcedure;
+
 /**
  * A G2D scene graph node that spatially decomposes all of its immediate child
  * nodes into an R-Tree structure to optimize the painting of its direct child
@@ -311,6 +312,31 @@ public class RTreeNode extends G2DParentNode implements INodeEventHandlerProvide
         return new Rectangle((float) rect.getMinX(), (float) rect.getMinY(), (float) rect.getMaxX(), (float) rect.getMaxY());
     }
 
+    public List<IG2DNode> intersectingNodes(Rectangle2D rect, List<IG2DNode> result) {
+        final Tree tree = getSpatialDecomposition();
+        if (rect == null || tree.bounds == null || containedBy(tree.bounds, rect)) {
+            IG2DNode[] nodes = getSortedNodes();
+            for (IG2DNode node : nodes) {
+                if (node.validate()) {
+                    result.add(node);
+                }
+            }
+        } else {
+            tree.rtree.intersects(toRectangle(rect), value -> {
+                //System.out.println("exec: " + value);
+                IG2DNode node = tree.toNodes.get(value);
+                //System.out.println("  node: " + node);
+                if (node == null || !node.validate())
+                    return true;
+
+                result.add(node);
+                return true;
+            });
+        }
+        Collections.sort(result, G2DParentNode.G2DNODE_Z_COMPARATOR);
+        return result;
+    }
+
     /**
      * Determine whether this rectangle is contained by the passed rectangle
      *