001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.ui.swingViewer.util; 033 034import java.awt.Graphics2D; 035import java.awt.geom.AffineTransform; 036import java.awt.geom.NoninvertibleTransformException; 037import java.awt.geom.Point2D; 038import java.util.ArrayList; 039import java.util.HashSet; 040 041import org.graphstream.graph.Node; 042import org.graphstream.ui.geom.Point2; 043import org.graphstream.ui.geom.Point3; 044import org.graphstream.ui.geom.Vector2; 045import org.graphstream.ui.graphicGraph.GraphicEdge; 046import org.graphstream.ui.graphicGraph.GraphicElement; 047import org.graphstream.ui.graphicGraph.GraphicGraph; 048import org.graphstream.ui.graphicGraph.GraphicNode; 049import org.graphstream.ui.graphicGraph.GraphicSprite; 050import org.graphstream.ui.graphicGraph.stylesheet.Style; 051import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants; 052import org.graphstream.ui.graphicGraph.stylesheet.Values; 053import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.Units; 054 055/** 056 * Define how the graph is viewed. 057 * 058 * <p> 059 * The camera is in charge of projecting the graph spaces in graph units (GU) 060 * into user spaces (often in pixels). It defines the transformation (an affine 061 * matrix) to passe from the first to the second. It also contains the graph 062 * metrics, a set of values that give the overall dimensions of the graph in 063 * graph units, as well as the view port, the area on the screen (or any 064 * rendering surface) that will receive the results in pixels (or rendering 065 * units). 066 * </p> 067 * 068 * <p> 069 * The camera defines a centre at which it always points. It can zoom on the 070 * graph, pan in any direction and rotate along two axes. 071 * </p> 072 * 073 * <p> 074 * Knowing the transformation also allows to provide services like "what element 075 * is not invisible ?" (not in the camera view) or "on what element is the mouse 076 * cursor actually ?". 077 * </p> 078 */ 079public class DefaultCamera implements Camera { 080 // Attribute 081 082 /** 083 * The graph. 084 */ 085 protected GraphicGraph graph = null; 086 087 /** 088 * Information on the graph overall dimension and position. 089 */ 090 protected GraphMetrics metrics = new GraphMetrics(); 091 092 /** 093 * Automatic centring of the view. 094 */ 095 protected boolean autoFit = true; 096 097 /** 098 * The camera centre of view. 099 */ 100 protected Point3 center = new Point3(); 101 102 /** 103 * The camera zoom. 104 */ 105 protected double zoom; 106 107 /** 108 * The graph-space -> pixel-space transformation. 109 */ 110 protected AffineTransform Tx = new AffineTransform(); 111 112 /** 113 * The inverse transform of Tx. 114 */ 115 protected AffineTransform xT; 116 117 /** 118 * The previous affine transform. 119 */ 120 protected AffineTransform oldTx; 121 122 /** 123 * The rotation angle. 124 */ 125 protected double rotation; 126 127 /** 128 * Padding around the graph. 129 */ 130 protected Values padding = new Values(Style.Units.GU, 0, 0, 0); 131 132 /** 133 * Which node is visible. This allows to mark invisible nodes to fasten 134 * visibility tests for nodes, attached sprites and edges. 135 */ 136 protected HashSet<String> nodeInvisible = new HashSet<String>(); 137 138 /** 139 * The graph view port, if any. The graph view port is a view inside the 140 * graph space. It allows to compute the view according to a specified area 141 * of the graph space instead of the graph dimensions. 142 */ 143 protected double gviewport[] = null; 144 protected double gviewportDiagonal = 0; 145 146 // Construction 147 148 /** 149 * New camera. 150 */ 151 public DefaultCamera(GraphicGraph graph) { 152 this.graph = graph; 153 } 154 155 // Access 156 157 /* 158 * (non-Javadoc) 159 * 160 * @see org.graphstream.ui.swingViewer.util.Camera#getViewCenter() 161 */ 162 public Point3 getViewCenter() { 163 return center; 164 } 165 166 /* 167 * (non-Javadoc) 168 * 169 * @see org.graphstream.ui.swingViewer.util.Camera#setViewCenter(double, 170 * double, double) 171 */ 172 public void setViewCenter(double x, double y, double z) { 173 setAutoFitView(false); 174 center.set(x, y, z); 175 graph.graphChanged = true; 176 } 177 178 public void setViewCenter(double x, double y) { 179 setViewCenter(x, y, 0); 180 } 181 182 /* 183 * (non-Javadoc) 184 * 185 * @see org.graphstream.ui.swingViewer.util.Camera#getViewPercent() 186 */ 187 public double getViewPercent() { 188 return zoom; 189 } 190 191 /* 192 * (non-Javadoc) 193 * 194 * @see org.graphstream.ui.swingViewer.util.Camera#setViewPercent(double) 195 */ 196 public void setViewPercent(double percent) { 197 setAutoFitView(false); 198 setZoom(percent); 199 graph.graphChanged = true; 200 } 201 202 /* 203 * (non-Javadoc) 204 * 205 * @see org.graphstream.ui.swingViewer.util.Camera#getViewRotation() 206 */ 207 public double getViewRotation() { 208 return rotation; 209 } 210 211 /* 212 * (non-Javadoc) 213 * 214 * @see org.graphstream.ui.swingViewer.util.Camera#getMetrics() 215 */ 216 public GraphMetrics getMetrics() { 217 return metrics; 218 } 219 220 @Override 221 public String toString() { 222 StringBuilder builder = new StringBuilder(String.format("Camera :%n")); 223 224 builder.append(String.format(" autoFit = %b%n", autoFit)); 225 builder.append(String.format(" center = %s%n", center)); 226 builder.append(String.format(" rotation = %f%n", rotation)); 227 builder.append(String.format(" zoom = %f%n", zoom)); 228 builder.append(String.format(" padding = %s%n", padding)); 229 builder.append(String.format(" metrics = %s%n", metrics)); 230 231 return builder.toString(); 232 } 233 234 /* 235 * (non-Javadoc) 236 * 237 * @see org.graphstream.ui.swingViewer.util.Camera#resetView() 238 */ 239 public void resetView() { 240 setAutoFitView(true); 241 setViewRotation(0); 242 } 243 244 /* 245 * (non-Javadoc) 246 * 247 * @see org.graphstream.ui.swingViewer.util.Camera#setBounds(double, double, 248 * double, double, double, double) 249 */ 250 public void setBounds(double minx, double miny, double minz, double maxx, 251 double maxy, double maxz) { 252 metrics.setBounds(minx, miny, minz, maxx, maxy, maxz); 253 } 254 255 /* 256 * (non-Javadoc) 257 * 258 * @see org.graphstream.ui.swingViewer.util.Camera#getGraphDimension() 259 */ 260 public double getGraphDimension() { 261 if (gviewport != null) 262 return gviewportDiagonal; 263 264 return metrics.diagonal; 265 } 266 267 /** 268 * True if the element should be visible on screen. The method used is to 269 * transform the center of the element (which is always in graph units) 270 * using the camera actual transformation to put it in pixel units. Then to 271 * look in the style sheet the size of the element and to test if its 272 * enclosing rectangle intersects the view port. For edges, its two nodes 273 * are used. As a speed-up by default if the camera is in automatic fitting 274 * mode, all element should be visible, and the test always returns true. 275 * 276 * @param element 277 * The element to test. 278 * @return True if the element is visible and therefore must be rendered. 279 */ 280 public boolean isVisible(GraphicElement element) { 281 if (autoFit) { 282 return ((!element.hidden) && (element.style.getVisibilityMode() != StyleConstants.VisibilityMode.HIDDEN)); 283 } else { 284 switch (element.getSelectorType()) { 285 case NODE: 286 return !nodeInvisible.contains(element.getId()); 287 case EDGE: 288 return isEdgeVisible((GraphicEdge) element); 289 case SPRITE: 290 return isSpriteVisible((GraphicSprite) element); 291 default: 292 return false; 293 } 294 } 295 } 296 297 /* 298 * (non-Javadoc) 299 * 300 * @see org.graphstream.ui.swingViewer.util.Camera#inverseTransform(double, 301 * double) 302 */ 303 public Point3 transformPxToGu(double x, double y) { 304 Point2D.Double p = new Point2D.Double(x, y); 305 xT.transform(p, p); 306 return new Point3(p.x, p.y, 0); 307 } 308 309 /* 310 * (non-Javadoc) 311 * 312 * @see org.graphstream.ui.swingViewer.util.Camera#transform(double, double) 313 */ 314 public Point3 transformGuToPx(double x, double y, double z) { 315 Point2D.Double p = new Point2D.Double(x, y); 316 Tx.transform(p, p); 317 return new Point3(p.x, p.y, 0); 318 } 319 320 /** 321 * Process each node to check if it is in the actual view port, and mark 322 * invisible nodes. This method allows for fast node, sprite and edge 323 * visibility checking when drawing. This must be called before each 324 * rendering (if the view port changed). 325 */ 326 public void checkVisibility(GraphicGraph graph) { 327 double X = metrics.viewport[0]; 328 double Y = metrics.viewport[1]; 329 double W = metrics.viewport[2]; 330 double H = metrics.viewport[3]; 331 332 nodeInvisible.clear(); 333 334 for (Node node : graph) { 335 boolean visible = isNodeIn((GraphicNode) node, X, Y, X + W, Y + H) 336 && (!((GraphicNode) node).hidden) 337 && ((GraphicNode) node).positionned; 338 339 if (!visible) 340 nodeInvisible.add(node.getId()); 341 } 342 } 343 344 /** 345 * Search for the first node or sprite (in that order) that contains the 346 * point at coordinates (x, y). 347 * 348 * @param graph 349 * The graph to search for. 350 * @param x 351 * The point abscissa. 352 * @param y 353 * The point ordinate. 354 * @return The first node or sprite at the given coordinates or null if 355 * nothing found. 356 */ 357 public GraphicElement findNodeOrSpriteAt(GraphicGraph graph, double x, 358 double y) { 359 for (Node n : graph) { 360 GraphicNode node = (GraphicNode) n; 361 362 if (nodeContains(node, x, y)) 363 return node; 364 } 365 366 for (GraphicSprite sprite : graph.spriteSet()) { 367 if (spriteContains(sprite, x, y)) 368 return sprite; 369 } 370 371 return null; 372 } 373 374 /** 375 * Search for all the nodes and sprites contained inside the rectangle 376 * (x1,y1)-(x2,y2). 377 * 378 * @param graph 379 * The graph to search for. 380 * @param x1 381 * The rectangle lowest point abscissa. 382 * @param y1 383 * The rectangle lowest point ordinate. 384 * @param x2 385 * The rectangle highest point abscissa. 386 * @param y2 387 * The rectangle highest point ordinate. 388 * @return The set of sprites and nodes in the given rectangle. 389 */ 390 public ArrayList<GraphicElement> allNodesOrSpritesIn(GraphicGraph graph, 391 double x1, double y1, double x2, double y2) { 392 ArrayList<GraphicElement> elts = new ArrayList<GraphicElement>(); 393 394 for (Node node : graph) { 395 if (isNodeIn((GraphicNode) node, x1, y1, x2, y2)) 396 elts.add((GraphicNode) node); 397 } 398 399 for (GraphicSprite sprite : graph.spriteSet()) { 400 if (isSpriteIn(sprite, x1, y1, x2, y2)) 401 elts.add(sprite); 402 } 403 404 return elts; 405 } 406 407 /** 408 * Compute the real position of a sprite according to its eventual 409 * attachment in graph units. 410 * 411 * @param sprite 412 * The sprite. 413 * @param pos 414 * Receiver for the sprite 2D position, can be null. 415 * @param units 416 * The units in which the position must be computed (the sprite 417 * already contains units). 418 * @return The same instance as the one given by parameter pos or a new one 419 * if pos was null, containing the computed position in the given 420 * units. 421 */ 422 public Point2D.Double getSpritePosition(GraphicSprite sprite, 423 Point2D.Double pos, Units units) { 424 if (sprite.isAttachedToNode()) 425 return getSpritePositionNode(sprite, pos, units); 426 else if (sprite.isAttachedToEdge()) 427 return getSpritePositionEdge(sprite, pos, units); 428 else 429 return getSpritePositionFree(sprite, pos, units); 430 } 431 432 public double[] getGraphViewport() { 433 return gviewport; 434 } 435 436 // Command 437 438 public void setGraphViewport(double minx, double miny, double maxx, 439 double maxy) { 440 setAutoFitView(false); 441 setViewCenter(minx + (maxx - minx) / 2.0, miny + (maxy - miny) / 2.0); 442 443 gviewport = new double[4]; 444 gviewport[0] = minx; 445 gviewport[1] = miny; 446 gviewport[2] = maxx; 447 gviewport[3] = maxy; 448 449 gviewportDiagonal = Math.sqrt((maxx - minx) * (maxx - minx) 450 + (maxy - miny) * (maxy - miny)); 451 452 setZoom(1); 453 } 454 455 public void removeGraphViewport() { 456 System.out.printf("gviewport removed\n"); 457 gviewport = null; 458 resetView(); 459 } 460 461 /** 462 * Set the camera view in the given graphics and backup the previous 463 * transform of the graphics. Call {@link #popView(Graphics2D)} to restore 464 * the saved transform. You can only push one time the view. 465 * 466 * @param g2 467 * The Swing graphics to change. 468 */ 469 public void pushView(GraphicGraph graph, Graphics2D g2) { 470 if (oldTx == null) { 471 oldTx = g2.getTransform(); // Backup the Swing transform. 472 473 if (autoFit) 474 autoFitView(g2); 475 else 476 userView(g2); 477 478 // g2.setTransform(Tx); // Set the final transform, a composition of 479 // the old Swing transform and our new coordinate system. 480 } else { 481 throw new RuntimeException( 482 "DefaultCamera.pushView() / popView() wrongly nested"); 483 } 484 485 checkVisibility(graph); 486 } 487 488 /** 489 * Restore the transform that was used before 490 * {@link #pushView(GraphicGraph, Graphics2D)} is used. 491 * 492 * @param g2 493 * The Swing graphics to restore. 494 */ 495 public void popView(Graphics2D g2) { 496 if (oldTx != null) { 497 g2.setTransform(oldTx); // Set back the old Swing Transform. 498 oldTx = null; 499 } 500 } 501 502 /** 503 * Compute a transformation matrix that pass from graph units (user space) 504 * to pixel units (device space) so that the whole graph is visible. 505 * 506 * @param g2 507 * The Swing graphics. 508 */ 509 protected void autoFitView(Graphics2D g2) { 510 double sx, sy; 511 double tx, ty; 512 double padXgu = getPaddingXgu() * 2; 513 double padYgu = getPaddingYgu() * 2; 514 double padXpx = getPaddingXpx() * 2; 515 double padYpx = getPaddingYpx() * 2; 516 517 sx = (metrics.viewport[2] - padXpx) / (metrics.size.data[0] + padXgu); // Ratio 518 // along 519 // X 520 sy = (metrics.viewport[3] - padYpx) / (metrics.size.data[1] + padYgu); // Ratio 521 // along 522 // Y 523 tx = metrics.lo.x + (metrics.size.data[0] / 2); // Centre of graph in X 524 ty = metrics.lo.y + (metrics.size.data[1] / 2); // Centre of graph in Y 525 526 if (sx > sy) // The least ratio. 527 sx = sy; 528 else 529 sy = sx; 530 531 g2.translate(metrics.viewport[2] / 2, metrics.viewport[3] / 2); 532 if (rotation != 0) 533 g2.rotate(rotation / (180 / Math.PI)); 534 g2.scale(sx, -sy); 535 g2.translate(-tx, -ty); 536 537 Tx = g2.getTransform(); 538 xT = new AffineTransform(Tx); 539 try { 540 xT.invert(); 541 } catch (NoninvertibleTransformException e) { 542 System.err.printf("cannot inverse gu2px matrix...%n"); 543 } 544 545 zoom = 1; 546 547 center.set(tx, ty, 0); 548 metrics.setRatioPx2Gu(sx); 549 metrics.loVisible.copy(metrics.lo); 550 metrics.hiVisible.copy(metrics.hi); 551 } 552 553 /** 554 * Compute a transformation that pass from graph units (user space) to a 555 * pixel units (device space) so that the view (zoom and centre) requested 556 * by the user is produced. 557 * 558 * @param g2 559 * The Swing graphics. 560 */ 561 protected void userView(Graphics2D g2) { 562 double sx, sy; 563 double tx, ty; 564 double padXgu = getPaddingXgu() * 2; 565 double padYgu = getPaddingYgu() * 2; 566 double padXpx = getPaddingXpx() * 2; 567 double padYpx = getPaddingYpx() * 2; 568 double gw = gviewport != null ? gviewport[2] - gviewport[0] 569 : metrics.size.data[0]; 570 double gh = gviewport != null ? gviewport[3] - gviewport[1] 571 : metrics.size.data[1]; 572 573 sx = (metrics.viewport[2] - padXpx) / ((gw + padXgu) * zoom); 574 sy = (metrics.viewport[3] - padYpx) / ((gh + padYgu) * zoom); 575 tx = center.x; 576 ty = center.y; 577 578 if (sx > sy) // The least ratio. 579 sx = sy; 580 else 581 sy = sx; 582 583 g2.translate((metrics.viewport[2] / 2), (metrics.viewport[3] / 2)); 584 if (rotation != 0) 585 g2.rotate(rotation / (180 / Math.PI)); 586 g2.scale(sx, -sy); 587 g2.translate(-tx, -ty); 588 589 Tx = g2.getTransform(); 590 xT = new AffineTransform(Tx); 591 try { 592 xT.invert(); 593 } catch (NoninvertibleTransformException e) { 594 System.err.printf("cannot inverse gu2px matrix...%n"); 595 } 596 597 metrics.setRatioPx2Gu(sx); 598 599 double w2 = (metrics.viewport[2] / sx) / 2; 600 double h2 = (metrics.viewport[3] / sx) / 2; 601 602 metrics.loVisible.set(center.x - w2, center.y - h2); 603 metrics.hiVisible.set(center.x + w2, center.y + h2); 604 } 605 606 /** 607 * Enable or disable automatic adjustment of the view to see the entire 608 * graph. 609 * 610 * @param on 611 * If true, automatic adjustment is enabled. 612 */ 613 public void setAutoFitView(boolean on) { 614 if (autoFit && (!on)) { 615 // We go from autoFit to user view, ensure the current centre is at 616 // the 617 // middle of the graph, and the zoom is at one. 618 619 zoom = 1; 620 center.set(metrics.lo.x + (metrics.size.data[0] / 2), metrics.lo.y 621 + (metrics.size.data[1] / 2), 0); 622 } 623 624 autoFit = on; 625 } 626 627 /** 628 * Set the zoom (or percent of the graph visible), 1 means the graph is 629 * fully visible. 630 * 631 * @param z 632 * The zoom. 633 */ 634 public void setZoom(double z) { 635 zoom = z; 636 graph.graphChanged = true; 637 } 638 639 /** 640 * Set the rotation angle around the centre. 641 * 642 * @param theta 643 * The rotation angle in degrees. 644 */ 645 public void setViewRotation(double theta) { 646 rotation = theta; 647 graph.graphChanged = true; 648 } 649 650 /** 651 * Set the output view port size in pixels. 652 * 653 * @param viewportWidth 654 * The width in pixels of the view port. 655 * @param viewportHeight 656 * The width in pixels of the view port. 657 */ 658 public void setViewport(double viewportX, double viewportY, 659 double viewportWidth, double viewportHeight) { 660 metrics.setViewport(viewportX, viewportY, viewportWidth, viewportHeight); 661 } 662 663 /** 664 * Set the graph padding. 665 * 666 * @param graph 667 * The graphic graph. 668 */ 669 public void setPadding(GraphicGraph graph) { 670 padding.copy(graph.getStyle().getPadding()); 671 } 672 673 // Utility 674 675 protected double getPaddingXgu() { 676 if (padding.units == Style.Units.GU && padding.size() > 0) 677 return padding.get(0); 678 679 return 0; 680 } 681 682 protected double getPaddingYgu() { 683 if (padding.units == Style.Units.GU && padding.size() > 1) 684 return padding.get(1); 685 686 return getPaddingXgu(); 687 } 688 689 protected double getPaddingXpx() { 690 if (padding.units == Style.Units.PX && padding.size() > 0) 691 return padding.get(0); 692 693 return 0; 694 } 695 696 protected double getPaddingYpx() { 697 if (padding.units == Style.Units.PX && padding.size() > 1) 698 return padding.get(1); 699 700 return getPaddingXpx(); 701 } 702 703 /** 704 * Check if a sprite is visible in the current view port. 705 * 706 * @param sprite 707 * The sprite to check. 708 * @return True if visible. 709 */ 710 protected boolean isSpriteVisible(GraphicSprite sprite) { 711 return isSpriteIn(sprite, metrics.viewport[0], metrics.viewport[1], 712 metrics.viewport[0] + metrics.viewport[2], metrics.viewport[1] 713 + metrics.viewport[3]); 714 } 715 716 /** 717 * Check if an edge is visible in the current view port. 718 * 719 * @param edge 720 * The edge to check. 721 * @return True if visible. 722 */ 723 protected boolean isEdgeVisible(GraphicEdge edge) { 724 GraphicNode node0 = edge.getNode0(); 725 GraphicNode node1 = edge.getNode1(); 726 727 if (edge.hidden) 728 return false; 729 730 if ((!node1.positionned) || (!node0.positionned)) 731 return false; 732 733 boolean node0Invis = nodeInvisible.contains(node0.getId()); 734 boolean node1Invis = nodeInvisible.contains(node1.getId()); 735 736 return !(node0Invis && node1Invis); 737 } 738 739 /** 740 * Is the given node visible in the given area. 741 * 742 * @param node 743 * The node to check. 744 * @param X1 745 * The min abscissa of the area. 746 * @param Y1 747 * The min ordinate of the area. 748 * @param X2 749 * The max abscissa of the area. 750 * @param Y2 751 * The max ordinate of the area. 752 * @return True if the node lies in the given area. 753 */ 754 protected boolean isNodeIn(GraphicNode node, double X1, double Y1, 755 double X2, double Y2) { 756 Values size = node.getStyle().getSize(); 757 double w2 = metrics.lengthToPx(size, 0) / 2; 758 double h2 = size.size() > 1 ? metrics.lengthToPx(size, 1) / 2 : w2; 759 Point2D.Double src = new Point2D.Double(node.getX(), node.getY()); 760 boolean vis = true; 761 762 Tx.transform(src, src); 763 764 double x1 = src.x - w2; 765 double x2 = src.x + w2; 766 double y1 = src.y - h2; 767 double y2 = src.y + h2; 768 769 if (x2 < X1) 770 vis = false; 771 else if (y2 < Y1) 772 vis = false; 773 else if (x1 > X2) 774 vis = false; 775 else if (y1 > Y2) 776 vis = false; 777 778 return vis; 779 } 780 781 /** 782 * Is the given sprite visible in the given area. 783 * 784 * @param sprite 785 * The sprite to check. 786 * @param X1 787 * The min abscissa of the area. 788 * @param Y1 789 * The min ordinate of the area. 790 * @param X2 791 * The max abscissa of the area. 792 * @param Y2 793 * The max ordinate of the area. 794 * @return True if the node lies in the given area. 795 */ 796 protected boolean isSpriteIn(GraphicSprite sprite, double X1, double Y1, 797 double X2, double Y2) { 798 if (sprite.isAttachedToNode() 799 && nodeInvisible.contains(sprite.getNodeAttachment().getId())) { 800 return false; 801 } else if (sprite.isAttachedToEdge() 802 && !isEdgeVisible(sprite.getEdgeAttachment())) { 803 return false; 804 } else { 805 Values size = sprite.getStyle().getSize(); 806 double w2 = metrics.lengthToPx(size, 0) / 2; 807 double h2 = size.size() > 1 ? metrics.lengthToPx(size, 1) / 2 : w2; 808 Point2D.Double src = spritePositionPx(sprite);// new Point2D.Double( 809 // sprite.getX(), 810 // sprite.getY() ); 811 812 // Tx.transform( src, src ); 813 814 double x1 = src.x - w2; 815 double x2 = src.x + w2; 816 double y1 = src.y - h2; 817 double y2 = src.y + h2; 818 819 if (x2 < X1) 820 return false; 821 if (y2 < Y1) 822 return false; 823 if (x1 > X2) 824 return false; 825 if (y1 > Y2) 826 return false; 827 828 return true; 829 } 830 } 831 832 protected Point2D.Double spritePositionPx(GraphicSprite sprite) { 833 Point2D.Double pos = new Point2D.Double(); 834 835 return getSpritePosition(sprite, pos, Units.PX); 836 // if( sprite.getUnits() == Units.PX ) 837 // { 838 // return new Point2D.Double( sprite.getX(), sprite.getY() ); 839 // } 840 // else if( sprite.getUnits() == Units.GU ) 841 // { 842 // Point2D.Double pos = new Point2D.Double( sprite.getX(), sprite.getY() 843 // ); 844 // return (Point2D.Double) Tx.transform( pos, pos ); 845 // } 846 // else// if( sprite.getUnits() == Units.PERCENTS ) 847 // { 848 // return new Point2D.Double( 849 // (sprite.getX()/100f)*metrics.viewport.data[0], 850 // (sprite.getY()/100f)*metrics.viewport.data[1] ); 851 // } 852 } 853 854 /** 855 * Check if a node contains the given point (x,y). 856 * 857 * @param elt 858 * The node. 859 * @param x 860 * The point abscissa. 861 * @param y 862 * The point ordinate. 863 * @return True if (x,y) is in the given element. 864 */ 865 protected boolean nodeContains(GraphicElement elt, double x, double y) { 866 Values size = elt.getStyle().getSize(); 867 double w2 = metrics.lengthToPx(size, 0) / 2; 868 double h2 = size.size() > 1 ? metrics.lengthToPx(size, 1) / 2 : w2; 869 Point2D.Double src = new Point2D.Double(elt.getX(), elt.getY()); 870 Point2D.Double dst = new Point2D.Double(); 871 872 Tx.transform(src, dst); 873 874 dst.x -= metrics.viewport[0]; 875 dst.y -= metrics.viewport[1]; 876 877 double x1 = dst.x - w2; 878 double x2 = dst.x + w2; 879 double y1 = dst.y - h2; 880 double y2 = dst.y + h2; 881 882 if (x < x1) 883 return false; 884 if (y < y1) 885 return false; 886 if (x > x2) 887 return false; 888 if (y > y2) 889 return false; 890 891 return true; 892 } 893 894 protected boolean edgeContains(GraphicElement elt, double x, double y) { 895 return false; 896 } 897 898 /** 899 * Check if a sprite contains the given point (x,y). 900 * 901 * @param elt 902 * The sprite. 903 * @param x 904 * The point abscissa. 905 * @param y 906 * The point ordinate. 907 * @return True if (x,y) is in the given element. 908 */ 909 protected boolean spriteContains(GraphicElement elt, double x, double y) { 910 Values size = elt.getStyle().getSize(); 911 double w2 = metrics.lengthToPx(size, 0) / 2; 912 double h2 = size.size() > 1 ? metrics.lengthToPx(size, 1) / 2 : w2; 913 Point2D.Double dst = spritePositionPx((GraphicSprite) elt); // new 914 // Point2D.Double( 915 // elt.getX(), 916 // elt.getY() 917 // ); 918 // Point2D.Double dst = new Point2D.Double(); 919 920 // Tx.transform( src, dst ); 921 dst.x -= metrics.viewport[0]; 922 dst.y -= metrics.viewport[1]; 923 924 double x1 = dst.x - w2; 925 double x2 = dst.x + w2; 926 double y1 = dst.y - h2; 927 double y2 = dst.y + h2; 928 929 if (x < x1) 930 return false; 931 if (y < y1) 932 return false; 933 if (x > x2) 934 return false; 935 if (y > y2) 936 return false; 937 938 return true; 939 } 940 941 /** 942 * Compute the position of a sprite if it is not attached. 943 * 944 * @param sprite 945 * The sprite. 946 * @param pos 947 * Where to stored the computed position, if null, the position 948 * is created. 949 * @param units 950 * The units the computed position must be given into. 951 * @return The same instance as pos, or a new one if pos was null. 952 */ 953 protected Point2D.Double getSpritePositionFree(GraphicSprite sprite, 954 Point2D.Double pos, Units units) { 955 if (pos == null) 956 pos = new Point2D.Double(); 957 958 if (sprite.getUnits() == units) { 959 pos.x = sprite.getX(); 960 pos.y = sprite.getY(); 961 } else if (units == Units.GU && sprite.getUnits() == Units.PX) { 962 pos.x = sprite.getX(); 963 pos.y = sprite.getY(); 964 965 xT.transform(pos, pos); 966 } else if (units == Units.PX && sprite.getUnits() == Units.GU) { 967 pos.x = sprite.getX(); 968 pos.y = sprite.getY(); 969 970 Tx.transform(pos, pos); 971 } else if (units == Units.GU && sprite.getUnits() == Units.PERCENTS) { 972 pos.x = metrics.lo.x + (sprite.getX() / 100f) 973 * metrics.graphWidthGU(); 974 pos.y = metrics.lo.y + (sprite.getY() / 100f) 975 * metrics.graphHeightGU(); 976 } else if (units == Units.PX && sprite.getUnits() == Units.PERCENTS) { 977 pos.x = (sprite.getX() / 100f) * metrics.viewport[2]; 978 pos.y = (sprite.getY() / 100f) * metrics.viewport[3]; 979 } else { 980 throw new RuntimeException("Unhandled yet sprite positioning."); 981 } 982 983 return pos; 984 } 985 986 /** 987 * Compute the position of a sprite if attached to a node. 988 * 989 * @param sprite 990 * The sprite. 991 * @param pos 992 * Where to stored the computed position, if null, the position 993 * is created. 994 * @param units 995 * The units the computed position must be given into. 996 * @return The same instance as pos, or a new one if pos was null. 997 */ 998 protected Point2D.Double getSpritePositionNode(GraphicSprite sprite, 999 Point2D.Double pos, Units units) { 1000 if (pos == null) 1001 pos = new Point2D.Double(); 1002 1003 GraphicNode node = sprite.getNodeAttachment(); 1004 double radius = metrics.lengthToGu(sprite.getX(), sprite.getUnits()); 1005 double z = (double) (sprite.getZ() * (Math.PI / 180f)); 1006 1007 pos.x = node.x + ((double) Math.cos(z) * radius); 1008 pos.y = node.y + ((double) Math.sin(z) * radius); 1009 1010 if (units == Units.PX) 1011 Tx.transform(pos, pos); 1012 1013 return pos; 1014 } 1015 1016 /** 1017 * Compute the position of a sprite if attached to an edge. 1018 * 1019 * @param sprite 1020 * The sprite. 1021 * @param pos 1022 * Where to stored the computed position, if null, the position 1023 * is created. 1024 * @param units 1025 * The units the computed position must be given into. 1026 * @return The same instance as pos, or a new one if pos was null. 1027 */ 1028 protected Point2D.Double getSpritePositionEdge(GraphicSprite sprite, 1029 Point2D.Double pos, Units units) { 1030 if (pos == null) 1031 pos = new Point2D.Double(); 1032 1033 GraphicEdge edge = sprite.getEdgeAttachment(); 1034 1035 if (edge.isCurve()) { 1036 double ctrl[] = edge.getControlPoints(); 1037 Point2 p0 = new Point2(edge.from.getX(), edge.from.getY()); 1038 Point2 p1 = new Point2(ctrl[0], ctrl[1]); 1039 Point2 p2 = new Point2(ctrl[1], ctrl[2]); 1040 Point2 p3 = new Point2(edge.to.getX(), edge.to.getY()); 1041 Vector2 perp = CubicCurve.perpendicular(p0, p1, p2, p3, 1042 sprite.getX()); 1043 double y = metrics.lengthToGu(sprite.getY(), sprite.getUnits()); 1044 1045 perp.normalize(); 1046 perp.scalarMult(y); 1047 1048 pos.x = CubicCurve.eval(p0.x, p1.x, p2.x, p3.x, sprite.getX()) 1049 - perp.data[0]; 1050 pos.y = CubicCurve.eval(p0.y, p1.y, p2.y, p3.y, sprite.getX()) 1051 - perp.data[1]; 1052 } else { 1053 double x = ((GraphicNode) edge.getSourceNode()).x; 1054 double y = ((GraphicNode) edge.getSourceNode()).y; 1055 double dx = ((GraphicNode) edge.getTargetNode()).x - x; 1056 double dy = ((GraphicNode) edge.getTargetNode()).y - y; 1057 double d = sprite.getX(); // Percent on the edge. 1058 double o = metrics.lengthToGu(sprite.getY(), sprite.getUnits()); 1059 // Offset from the position given by percent, perpendicular to the 1060 // edge. 1061 1062 d = d > 1 ? 1 : d; 1063 d = d < 0 ? 0 : d; 1064 1065 x += dx * d; 1066 y += dy * d; 1067 1068 d = (double) Math.sqrt(dx * dx + dy * dy); 1069 dx /= d; 1070 dy /= d; 1071 1072 x += -dy * o; 1073 y += dx * o; 1074 1075 pos.x = x; 1076 pos.y = y; 1077 1078 if (units == Units.PX) { 1079 Tx.transform(pos, pos); 1080 } 1081 } 1082 1083 return pos; 1084 } 1085}