Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -338,110 +338,153 @@ protected void drawEnvOnView(final Graphics2D g) {
return;
}
accessData();
if (hooked.isPresent()) {
final P hcoor = positions.get(hooked.get());
final Point hp = wormhole.getViewPoint(hcoor);
if (hp.distance(getCenter()) > FREEDOM_RADIUS) {
wormhole.setViewPosition(hp);
}
final Map<Node<T>, Point> onView;
try {
updateHookedNodeView();
onView = computeNodesOnView();
g.setColor(Color.BLACK);
drawObstacles(g);
drawLinks(g, onView);
} finally {
releaseData();
}
moveSelectedNodesOnView(onView);
g.setColor(Color.GREEN);
if (effectStack != null) {
effectStack.forEach(effect -> onView.forEach((node, point) ->
effect.apply(g, node, currentEnv, wormhole)));
}
highlightClosestNode(g, onView);
drawSelectionRectangle(g, onView);
highlightSelectedNodes(g, onView);
}

private Map<Node<T>, Point> computeNodesOnView() {
return positions.entrySet().parallelStream()
.map(pair -> new Pair<>(pair.getKey(), wormhole.getViewPoint(pair.getValue())))
.filter(p -> wormhole.isInsideView(p.getSecond()))
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
}

private void updateHookedNodeView() {
if (hooked.isEmpty()) {
return;
}
final Node<T> hookedNode = hooked.get();
final P hookedCoordinates = positions.get(hookedNode);
if (hookedCoordinates == null) {
hooked = Optional.empty();
return;
}
final Point hookedPoint = wormhole.getViewPoint(hookedCoordinates);
if (hookedPoint.distance(getCenter()) > FREEDOM_RADIUS) {
wormhole.setViewPosition(hookedPoint);
}
}

private void drawObstacles(final Graphics2D g) {
if (obstacles == null) {
return;
}
/*
* Compute nodes in sight and their screen position
* This currently draws all obstacles, even when they are fully outside the viewport.
*/
final Map<Node<T>, Point> onView = positions.entrySet().parallelStream()
.map(pair -> new Pair<>(pair.getKey(), wormhole.getViewPoint(pair.getValue())))
.filter(p -> wormhole.isInsideView(p.getSecond()))
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
g.setColor(Color.BLACK);
if (obstacles != null) {
/*
* TODO: only draw obstacles if on view
*/
obstacles.parallelStream()
.map(this::convertObstacle)
.forEachOrdered(g::fill);
}
if (paintLinks) {
g.setColor(Color.GRAY);
onView.keySet().parallelStream()
.map(neighbors::get)
.flatMap(neigh ->
neigh.getNeighbors().parallelStream()
.map(node ->
node.compareTo(neigh.getCenter()) > 0
? new Pair<>(neigh.getCenter(), node)
: new Pair<>(node, neigh.getCenter())
)
)
.distinct()
.map(pair ->
mapPair(
pair,
node -> Optional
.ofNullable(onView.get(node))
.orElse(wormhole.getViewPoint(positions.get(node)))
final List<Shape> obstacleShapes = obstacles.parallelStream()
.map(this::convertObstacle)
.collect(Collectors.toList());
obstacleShapes.forEach(g::fill);
}

private void drawLinks(final Graphics2D g, final Map<Node<T>, Point> onView) {
if (!paintLinks) {
return;
}
g.setColor(Color.GRAY);
onView.keySet().parallelStream()
.map(neighbors::get)
.flatMap(neigh ->
neigh.getNeighbors().parallelStream()
.map(node ->
node.compareTo(neigh.getCenter()) > 0
? new Pair<>(neigh.getCenter(), node)
: new Pair<>(node, neigh.getCenter())
)
)
.distinct()
.map(pair ->
mapPair(
pair,
node -> Optional
.ofNullable(onView.get(node))
.orElse(wormhole.getViewPoint(positions.get(node)))
)
.forEachOrdered(line -> {
final Point p1 = line.getFirst();
final Point p2 = line.getSecond();
g.drawLine(p1.x, p1.y, p2.x, p2.y);
});
)
.forEachOrdered(line -> {
final Point p1 = line.getFirst();
final Point p2 = line.getSecond();
g.drawLine(p1.x, p1.y, p2.x, p2.y);
});
Comment thread
DanySK marked this conversation as resolved.
}

private void moveSelectedNodesOnView(final Map<Node<T>, Point> onView) {
if (!isDraggingMouse || status != ViewStatus.MOVING_SELECTED_NODES || originPoint == null || endingPoint == null) {
return;
}
releaseData();
if (
isDraggingMouse
&& status == ViewStatus.MOVING_SELECTED_NODES
&& originPoint != null
&& endingPoint != null
) {
for (final Node<T> n : selectedNodes) {
if (onView.containsKey(n)) {
onView.put(n, new Point(onView.get(n).x + (endingPoint.x - originPoint.x),
onView.get(n).y + (endingPoint.y - originPoint.y)));
}
for (final Node<T> node : selectedNodes) {
final Point point = onView.get(node);
if (point != null) {
onView.put(
node,
new Point(
point.x + (endingPoint.x - originPoint.x),
point.y + (endingPoint.y - originPoint.y)
)
);
}
}
g.setColor(Color.GREEN);
if (effectStack != null) {
effectStack.forEach(effect -> onView.forEach((node, point) ->
effect.apply(g, node, currentEnv, wormhole)));
}
if (isCloserNodeMarked()) {
final Optional<Map.Entry<Node<T>, Point>> closest = onView.entrySet().parallelStream()
.min((pair1, pair2) -> {
final Point p1 = pair1.getValue();
final Point p2 = pair2.getValue();
final double d1 = Math.hypot(p1.x - mouseX, p1.y - mouseY);
final double d2 = Math.hypot(p2.x - mouseX, p2.y - mouseY);
return Double.compare(d1, d2);
});
if (closest.isPresent()) {
nearest = closest.get().getKey();
final int nearestX = closest.get().getValue().x;
final int nearestY = closest.get().getValue().y;
drawFriedEgg(g, nearestX, nearestY, Color.RED, Color.YELLOW);
}
} else {
}

private void highlightClosestNode(final Graphics2D g, final Map<Node<T>, Point> onView) {
if (!isCloserNodeMarked()) {
nearest = null;
return;
}
if (isDraggingMouse && status == ViewStatus.SELECTING_NODES && originPoint != null && endingPoint != null) {
g.setColor(Color.BLACK);
final int x = Math.min(originPoint.x, endingPoint.x);
final int y = Math.min(originPoint.y, endingPoint.y);
final int width = Math.abs(endingPoint.x - originPoint.x);
final int height = Math.abs(endingPoint.y - originPoint.y);
g.drawRect(x, y, width, height);
selectedNodes = onView.entrySet().parallelStream()
.filter(nodes -> isInsideRectangle(nodes.getValue(), x, y, width, height))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
onView.entrySet().parallelStream()
.min((pair1, pair2) -> Double.compare(distanceFromMouse(pair1.getValue()), distanceFromMouse(pair2.getValue())))
.ifPresent(closest -> {
nearest = closest.getKey();
final Point point = closest.getValue();
drawFriedEgg(g, point.x, point.y, Color.RED, Color.YELLOW);
});
}

private double distanceFromMouse(final Point point) {
return Math.hypot(point.x - mouseX, point.y - mouseY);
}

private void drawSelectionRectangle(final Graphics2D g, final Map<Node<T>, Point> onView) {
if (!isDraggingMouse || status != ViewStatus.SELECTING_NODES || originPoint == null || endingPoint == null) {
return;
}
g.setColor(Color.BLACK);
final int x = Math.min(originPoint.x, endingPoint.x);
final int y = Math.min(originPoint.y, endingPoint.y);
final int width = Math.abs(endingPoint.x - originPoint.x);
final int height = Math.abs(endingPoint.y - originPoint.y);
g.drawRect(x, y, width, height);
selectedNodes = onView.entrySet().parallelStream()
.filter(nodes -> isInsideRectangle(nodes.getValue(), x, y, width, height))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}

private void highlightSelectedNodes(final Graphics2D g, final Map<Node<T>, Point> onView) {
selectedNodes.parallelStream()
.map(e -> Optional.ofNullable(onView.get(e)))
.map(node -> Optional.ofNullable(onView.get(node)))
.filter(Optional::isPresent)
.map(Optional::get)
.forEachOrdered(p -> drawFriedEgg(g, p.x, p.y, Color.BLUE, Color.CYAN));
.collect(Collectors.toList())
.forEach(point -> drawFriedEgg(g, point.x, point.y, Color.BLUE, Color.CYAN));
}

private void drawFriedEgg(final Graphics g, final int x, final int y, final Color c1, final Color c2) {
Expand Down Expand Up @@ -899,7 +942,7 @@ public void mouseReleased(final MouseEvent e) {
});
}
} else {
// TODO: display proper error message
// Surface this warning through the UI if interactive feedback is added here.
L.warn("Can not handle node movement on a finished simulation.");
}
} else {
Expand Down
Loading