From 48135dcd03588783f9c1b688aaa53cdaacba6ef2 Mon Sep 17 00:00:00 2001 From: jsimomaa Date: Fri, 27 Jul 2018 10:14:49 +0300 Subject: [PATCH] Performance and resource consumption optimization for G2D picking * 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 --- .../diagram/participant/ConnectTool2.java | 1 + .../participant/ConnectionEditingSupport.java | 8 +- .../participant/PointerInteractor2.java | 3 +- .../participant/RouteGraphConnectTool.java | 4 +- .../simantics/g2d/diagram/DiagramHints.java | 14 + .../simantics/g2d/diagram/DiagramUtils.java | 2 +- .../g2d/diagram/handler/PickRequest.java | 12 + .../diagram/handler/impl/PickContextImpl.java | 428 ++++++++++++------ .../participant/ElementInteractor.java | 2 +- .../diagram/participant/ElementPainter.java | 42 +- .../diagram/participant/TerminalPainter.java | 4 +- .../diagram/participant/ZOrderHandler.java | 6 +- .../pointertool/BoxSelectionMode.java | 2 +- .../pointertool/PointerInteractor.java | 12 +- .../participant/pointertool/TerminalUtil.java | 13 +- .../g2d/scenegraph/SceneGraphConstants.java | 4 + .../ui/diagramEditor/TerminalInformer.java | 2 +- .../g2d/nodes/spatial/RTreeNode.java | 32 +- 18 files changed, 418 insertions(+), 173 deletions(-) diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java index 7d3a5a400..ce2630df6 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java @@ -577,6 +577,7 @@ public class ConnectTool2 extends AbstractMode { } } else { RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection( + getContext(), diagram, pi.getCanvasPickShape(me.controlPosition), pi.getPickDistance()); diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionEditingSupport.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionEditingSupport.java index d0307eb16..fe11570fe 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionEditingSupport.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionEditingSupport.java @@ -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 newBp = new AtomicReference(); try { - SimanticsUI.getSession().syncRequest(new WriteRequest() { + Simantics.getSession().syncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/PointerInteractor2.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/PointerInteractor2.java index 832e9aba9..e20d58d5b 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/PointerInteractor2.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/PointerInteractor2.java @@ -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); diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java index 8f99207e1..d1ba21741 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java @@ -839,9 +839,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 elements = new ArrayList(); - PickRequest req = new PickRequest(pickShape); + PickRequest req = new PickRequest(pickShape).context(ctx); DiagramUtils.pick(diagram, req, elements); for (Iterator it = elements.iterator(); it.hasNext();) { IElement e = it.next(); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java index 367cebcce..aded9d170 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramHints.java @@ -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"); + } diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java index 8cfea8b59..7f83cf9bc 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java @@ -332,7 +332,7 @@ public class DiagramUtils { } /** - * Execute the specified {@link Callback} within a diagram write transaction + * Execute the specified callback 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 diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java index a43e8350a..265caa805 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/PickRequest.java @@ -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); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/impl/PickContextImpl.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/impl/PickContextImpl.java index 1f6a76903..a891d62b7 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/impl/PickContextImpl.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/handler/impl/PickContextImpl.java @@ -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 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 result = pickElements(diagram, request); - Collection result = finalResult; - if (request.pickSorter != null) { - // Need a temporary List for PickSorter - result = new ArrayList(); + 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 elems = new ArrayList<>(result); + request.pickSorter.sort(elems); + finalResult.addAll(elems); + } else { + finalResult.addAll(result); + } + } + } + + private static class PickFilter implements Predicate { + + 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 pickHandlers = e.getElementClass().getItemsByClass(Pick.class); - if (!pickHandlers.isEmpty()) - { - // Rough filtering with bounds + List 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 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 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 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 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 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 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 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 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 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) 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; } } diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementInteractor.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementInteractor.java index caad1ef4b..ae9c1ce07 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementInteractor.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementInteractor.java @@ -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 result = new ArrayList(); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java index ba575c630..6e9ff85b1 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java @@ -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 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 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 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 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 childrenTemp = new ArrayList(); - public void addElement(IElement e, boolean synchronizeSceneGraphNow) { + public void addElement(IDiagram d, IElement e, boolean synchronizeSceneGraphNow, Map 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 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); } } diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java index 39baa912c..d9c707993 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/TerminalPainter.java @@ -189,7 +189,7 @@ public class TerminalPainter extends AbstractDiagramParticipant { // Paint terminals normally if (paintAreaTerminals || paintPointTerminals) { - List pickedTerminals = TerminalUtil.pickTerminals(diagram, null, paintPointTerminals, paintAreaTerminals); + List 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 tis = TerminalUtil.pickTerminals(diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals); + List tis = TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, paintHoverAreaTerminals, paintHoverPointTerminals); paintTerminals(node, Color.RED, diagram, canvasPickRect.getBounds2D(), tis, strategy); if(tis.size() > 0) repaint = true; } diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ZOrderHandler.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ZOrderHandler.java index e393b7c13..007d039f2 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ZOrderHandler.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ZOrderHandler.java @@ -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 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 pickedElements = new ArrayList(); - 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 pickedElements = new ArrayList(); - 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() { diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/BoxSelectionMode.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/BoxSelectionMode.java index 6a1d27fd2..d66b8fbbe 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/BoxSelectionMode.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/BoxSelectionMode.java @@ -84,7 +84,7 @@ public class BoxSelectionMode extends AbstractMode { boolean accumulate = (modifiers & MouseEvent.SHIFT_MASK) != 0; Set boxSelection = new HashSet(); - PickRequest request = new PickRequest(rect); + PickRequest request = new PickRequest(rect).context(getContext()); request.pickPolicy = boxSelectMode; pickContext.pick(diagram, request, boxSelection); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java index fc0f35cb0..4f9df238f 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java @@ -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 pick = new ArrayList(); 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 picks = new ArrayList(); 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); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java index 677c97f0f..da9829428 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java @@ -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 pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals) + public static List pickTerminals(ICanvasContext ctx, IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals) { boolean clearElements = false; List 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 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 elements = ELEMENTS.get(); elements.clear(); - PickRequest req = new PickRequest(pickShape); + PickRequest req = new PickRequest(pickShape).context(ctx); DiagramUtils.pick(diagram, req, elements); ArrayList bends = new ArrayList<>(); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/scenegraph/SceneGraphConstants.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/scenegraph/SceneGraphConstants.java index 6d975b100..2c082353e 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/scenegraph/SceneGraphConstants.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/scenegraph/SceneGraphConstants.java @@ -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; + } diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/TerminalInformer.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/TerminalInformer.java index c9699fe77..8a4944200 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/TerminalInformer.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/TerminalInformer.java @@ -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 { diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java index d9b34dee6..52403edd0 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java @@ -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 intersectingNodes(Rectangle2D rect, List 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 * -- 2.47.1