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}