All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graphstream.ui.j2dviewer.Camera.scala Maven / Gradle / Ivy

/*
 * Copyright 2006 - 2011 
 *     Julien Baudry	
 *     Antoine Dutot	
 *     Yoann Pigné		
 *     Guilhelm Savin	
 * 
 * This file is part of GraphStream .
 * 
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 * 
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL  or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */
package org.graphstream.ui.j2dviewer

import org.graphstream.ui.sgeom.Point3

//import java.awt.Graphics2D
//import java.awt.geom.{AffineTransform, Point2D}
import java.util.ArrayList

import scala.collection.mutable.HashSet
import scala.collection.JavaConversions._
import scala.math._

import org.graphstream.graph.Node
import org.graphstream.ui.graphicGraph.stylesheet.Selector.Type._

import org.graphstream.ui.graphicGraph.{GraphicEdge, GraphicElement, GraphicGraph, GraphicNode, GraphicSprite}
import org.graphstream.ui.graphicGraph.stylesheet.{Style, Values}
import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants._

import org.graphstream.ui.util.{GraphMetrics, CubicCurve}
import org.graphstream.ui.geom.Point2
import org.graphstream.ui.sgeom.Vector2
import org.graphstream.ui.j2dviewer.renderer.{ElementInfo, EdgeInfo, NodeInfo}

import scala.math._
import org.graphstream.ScalaGS._


/**
 * Define how the graph is viewed.
 * 
 * 

* The camera is in charge of projecting the graph elements in graph units (GU) into * user spaces (often in pixels). It defines the transformation (an affine matrix) to pass * from the first to the second. It also contains the graph metrics, a set of values that * give the overall dimensions of the graph in graph units, as well as the view port, the * area on the screen (or any rendering surface) that will receive the results in pixels * (or rendering units). The two mains methods for this operation are * {@link #pushView(Graphics2D,GraphicGraph)} and {@link #popView()}. *

* *

* The user of the camera must set both the view port and the graph bounds in order for the * camera to correctly project the graph view. The camera also defines a centre at which it * always points. It can zoom on the graph, pan in any direction and rotate along two axes. *

* *

* There are two modes : an "auto-fit" mode where the camera always show the whole graph even * if it changes in size, and a "user" mode where the camera centre (looked-at point), zoom and * panning are specified. *

* *

* Knowing the transformation also allows to provide services like "what element is * visible ?" (in the camera view) or "on what element is the mouse cursor actually ?". *

* *

* The camera is also able to compute sprite positions according to their attachment, as well as * maintaining a list of all elements out of the view, so that it is not needed to render them. *

*/ class Camera { // Attribute /** Information on the graph overall dimension and position. */ val metrics = new GraphMetrics /** Automatic centring of the view. */ protected var autoFit = true /** The camera centre of view. */ protected val center = new Point3 /** The camera zoom. */ protected var zoom:Double = 1 /** The rendering back-end. */ protected var bck:Backend = null /** The graph-space -> pixel-space transformation. */ // protected var Tx = new AffineTransform /** The inverse transform of Tx. */ // protected var xT:AffineTransform = null /** The previous affine transform. */ // protected var oldTx:AffineTransform = null /** The rotation angle. */ protected var rotation:Double = 0 /** Padding around the graph. */ protected var padding = new Values(Units.GU, 0, 0, 0); /** * Which node is visible. This allows to mark invisible nodes to fasten visibility tests for * nodes, attached sprites and edges. The visibility test is heavy, and we often need to test * for nodes visibility. This allows to do it only once per rendering step. Hence the storage * of the invisible nodes here. */ protected val nodeInvisible = new HashSet[String] /** * The graph view port, if any. The graph view port is a view inside the graph space. It allows * to compute the view according to a specified area of the graph space instead of the graph * dimensions. */ protected var gviewport:Array[Double] = null // Access /** * The view centre (a point in graph units). * @return The view centre. */ def viewCenter:Point3 = center /** * The visible portion of the graph. * @return A real for which value 1 means the graph is fully visible and uses the whole * view port. */ def viewPercent:Double = zoom /** * The rotation angle in degrees. * @return The rotation angle in degrees. */ def viewRotation:Double = rotation override def toString():String = { var builder = new StringBuilder( "Camera :%n".format() ) builder.append( " autoFit = %b%n".format( autoFit ) ) builder.append( " center = %s%n".format( center ) ) builder.append( " rotation = %f%n".format( rotation ) ) builder.append( " zoom = %f%n".format( zoom ) ) builder.append( " padding = %s%n".format( padding ) ) builder.append( " metrics = %s%n".format( metrics ) ) builder.toString } /** * True if the element would be visible on screen. The method used is to transform the centre * of the element (which is always in graph units) using the camera actual transformation to * put it in pixel units. Then to look in the style sheet the size of the element and to test * if its enclosing rectangle intersects the view port. For edges, its two nodes are used. * @param element The element to test. * @return True if the element is visible and therefore must be rendered. */ def isVisible(element:GraphicElement):Boolean = { if( styleVisible( element ) ) element.getSelectorType match { case NODE => ! nodeInvisible.contains(element.getId) case EDGE => isEdgeVisible(element.asInstanceOf[GraphicEdge]) case SPRITE => isSpriteVisible(element.asInstanceOf[GraphicSprite]) case _ => false } else false } /** * Return the given point in pixels converted in graph units (GU) using the inverse * transformation of the current projection matrix. The inverse matrix is computed only * once each time a new projection matrix is created. * @param x The source point abscissa in pixels. * @param y The source point ordinate in pixels. * @return The resulting points in graph units. */ def inverseTransform(x:Double, y:Double):Point3 = bck.inverseTransform(x, y, 0) /** * Transform a point in graph units into pixels. * @return The transformed point. */ def transform(x:Double, y:Double):Point3 = bck.transform(x, y, 0) /** * Search for the first node or sprite (in that order) that contains the point at coordinates * (x, y). * @param graph The graph to search for. * @param x The point abscissa. * @param y The point ordinate. * @return The first node or sprite at the given coordinates or null if nothing found. */ def findNodeOrSpriteAt(graph:GraphicGraph, x:Double, y:Double):GraphicElement = { var ge:GraphicElement = null graph.getEachNode.foreach { n => val node = n.asInstanceOf[GraphicNode] if( nodeContains( node, x, y ) ) ge = node } graph.spriteSet.foreach { sprite => if( spriteContains( sprite, x, y ) ) ge = sprite } ge } /** * Search for all the nodes and sprites contained inside the rectangle (x1,y1)-(x2,y2). * @param graph The graph to search for. * @param x1 The rectangle lowest point abscissa. * @param y1 The rectangle lowest point ordinate. * @param x2 The rectangle highest point abscissa. * @param y2 The rectangle highest point ordinate. * @return The set of sprites and nodes in the given rectangle. */ def allNodesOrSpritesIn(graph:GraphicGraph, x1:Double, y1:Double, x2:Double, y2:Double):ArrayList[GraphicElement] = { val elts = new ArrayList[GraphicElement] graph.getEachNode.foreach { node:Node => if(isNodeIn(node.asInstanceOf[GraphicNode], x1, y1, x2, y2)) elts.add( node.asInstanceOf[GraphicNode]) } graph.spriteSet.foreach { sprite:GraphicSprite => if(isSpriteIn(sprite, x1, y1, x2, y2)) elts.add(sprite) } elts } /** * Compute the real position of a sprite according to its eventual attachment in graph units. * @param sprite The sprite. * @param pos Receiver for the sprite 2D position, can be null. * @param units The units in which the position must be computed (the sprite already contains units). * @return The same instance as the one given by parameter pos or a new one if pos was null, * containing the computed position in the given units. */ def getSpritePosition(sprite:GraphicSprite, pos:Point3, units:Units):Point3 = { if( sprite.isAttachedToNode() ) getSpritePositionNode(sprite, pos, units) else if( sprite.isAttachedToEdge() ) getSpritePositionEdge(sprite, pos, units) else getSpritePositionFree(sprite, pos, units) } def graphViewport = gviewport // Command def setBackend(backend:Backend) { bck = backend } def setGraphViewport(minx:Double, miny:Double, maxx:Double, maxy:Double) { gviewport = Array( minx, miny, maxx, maxy ) } def removeGraphViewport() { gviewport = null } /** * Set the camera view in the given graphics and backup the previous transform of the graphics. * Call {@link #popView(Graphics2D)} to restore the saved transform. You can only push one time * the view. * @param g2 The Swing graphics to change. * @param graph The graphic graph (used to check element visibility). */ def pushView(graph:GraphicGraph) { bck.pushTransform setPadding(graph) if(autoFit) autoFitView else userView checkVisibility(graph) } /** * Restore the transform that was used before {@link #pushView(Graphics2D)} is used. * @param g2 The Swing graphics to restore. */ def popView() { bck.popTransform } /** * Compute a transformation matrix that pass from graph units (user space) to pixel units * (device space) so that the whole graph is visible. * @param g2 The Swing graphics. * @param Tx The transformation to modify. * @return The transformation modified. */ protected def autoFitView() { var sx = 0.0; var sy = 0.0 var tx = 0.0; var ty = 0.0 val padXgu = paddingXgu * 2 val padYgu = paddingYgu * 2 val padXpx = paddingXpx * 2 val padYpx = paddingYpx * 2 sx = (metrics.viewport(0) - padXpx) / (metrics.size(0) + padXgu) // Ratio along X sy = (metrics.viewport(1) - padYpx) / (metrics.size(1) + padYgu) // Ratio along Y tx = metrics.lo.x + (metrics.size(0) / 2) // Centre of graph in X ty = metrics.lo.y + (metrics.size(1) / 2) // Centre of graph in Y if(sx > sy) // The least ratio. sx = sy else sy = sx bck.beginTransform bck.setIdentity bck.translate(metrics.viewport(0)/2, metrics.viewport(1)/2, 0) // 4. Place the whole result at the centre of the view port. if(rotation != 0) bck.rotate(rotation/(180.0/Pi), 0, 0, 1) // 3. Eventually apply a Z axis rotation. bck.scale(sx, -sy, 0) // 2. Scale the graph to pixels. Scale -y since we reverse the view (top-left to bottom-keft). bck.translate(-tx, -ty, 0) // 1. Move the graph so that its real centre is at (0,0). bck.endTransform // Tx.setToIdentity // Tx.translate( metrics.viewport.data(0) / 2, // metrics.viewport.data(1) / 2 ) // 4. Place the whole result at the centre of the view port. // if( rotation != 0 ) // Tx.rotate( rotation/(180/Pi) ) // 3. Eventually apply a rotation. // Tx.scale( sx, -sy ) // 2. Scale the graph to pixels. Scale -y since we reverse the view (top-left to bottom-left). // Tx.translate( -tx, -ty ) // 1. Move the graph so that its real centre is at (0,0). // // xT = new AffineTransform( Tx ) // try { xT.invert } catch { case _ => System.err.printf( "cannot inverse gu2px matrix...%n" ) } zoom = 1 center.set(tx, ty, 0) metrics.ratioPx2Gu = sx metrics.loVisible.copy(metrics.lo) metrics.hiVisible.copy(metrics.hi) // Tx } /** * Compute a transformation that pass from graph units (user space) to a pixel units (device * space) so that the view (zoom and centre) requested by the user is produced. * @param g2 The Swing graphics. * @param Tx The transformation to modify. * @return The transformation modified. */ protected def userView() { var sx = 0.0; var sy = 0.0 var tx = 0.0; var ty = 0.0 val padXgu = paddingXgu * 2 val padYgu = paddingYgu * 2 val padXpx = paddingXpx * 2 val padYpx = paddingYpx * 2 val gw = if(gviewport ne null) gviewport(2)-gviewport(0) else metrics.size(0) val gh = if(gviewport ne null) gviewport(3)-gviewport(1) else metrics.size(1) // val diag = Math.max( metrics.size.data(0)+padXgu, metrics.size.data(1)+padYgu ) * zoom // // sx = (metrics.viewport(0) - padXpx) / diag // sy = (metrics.viewport(1) - padYpx) / diag sx = (metrics.viewport(0) - padXpx) / ((gw + padXgu) * zoom) sy = (metrics.viewport(1) - padYpx) / ((gh + padYgu) * zoom) tx = center.x ty = center.y if(sx > sy) // The least ratio. sx = sy; else sy = sx; bck.beginTransform bck.setIdentity bck.translate(metrics.viewport(0)/2, metrics.viewport(1)/2, 0) // 4. Place the whole result at the centre of the view port. if(rotation != 0) bck.rotate(rotation/(180.0/Pi), 0, 0, 1) // 3. Eventually apply a rotation. bck.scale(sx, -sy, 0) // 2. Scale the graph to pixels. Scale -y since we reverse the view (top-left to bottom-left). bck.translate(-tx, -ty, 0) // 1. Move the graph so that the give centre is at (0,0). bck.endTransform // Tx.setToIdentity // Tx.translate( metrics.viewport.data(0) / 2, // metrics.viewport.data(1) / 2 ) // 4. Place the whole result at the centre of the view port. // if( rotation != 0 ) // Tx.rotate( rotation/(180/Pi) ); // 3. Eventually apply a rotation. // Tx.scale( sx, -sy ) // 2. Scale the graph to pixels. Scale -y since we reverse the view (top-left to bottom-left). // Tx.translate( -tx, -ty ) // 1. Move the graph so that the given centre is at (0,0). // // xT = new AffineTransform( Tx ) // try { xT.invert } catch { case _ => System.err.printf( "cannot inverse gu2px matrix...%n" ) } metrics.ratioPx2Gu = sx val w2 = (metrics.viewport(0) / sx) / 2f val h2 = (metrics.viewport(1) / sx) / 2f metrics.loVisible.set(center.x-w2, center.y-h2) metrics.hiVisible.set(center.x+w2, center.y+h2) // Tx } /** * Enable or disable automatic adjustment of the view to see the entire graph. * @param on If true, automatic adjustment is enabled. */ def setAutoFitView(on:Boolean) { if(autoFit && (! on)) { // We go from autoFit to user view, ensure the current centre is at the // middle of the graph, and the zoom is at one. zoom = 1 center.set(metrics.lo.x + (metrics.size(0) / 2), metrics.lo.y + (metrics.size(1) / 2), 0); } autoFit = on } /** * Set the centre of the view (the looked at point). As the viewer is only 2D, the z value is * not required. * @param x The new position abscissa. * @param y The new position ordinate. */ def setViewCenter(x:Double, y:Double) { center.set(x, y, 0) } /** * Set the zoom (or percent of the graph visible), 1 means the graph is fully visible. * @param z The zoom. */ def viewPercent_=(z:Double) { zoom = z } /** * Set the rotation angle around the centre. * @param angle The rotation angle in degrees. */ def viewRotation_=(angle:Double) { rotation = angle } /** * Set the output view port size in pixels. * @param viewportWidth The width in pixels of the view port. * @param viewportHeight The width in pixels of the view port. */ def setViewport(viewportWidth:Double, viewportHeight:Double) { metrics.setViewport(viewportWidth, viewportHeight) } /** * Set the graphic graph bounds (the lowest and highest points). * @param minx Lowest abscissa. * @param miny Lowest ordinate. * @param minz Lowest depth. * @param maxx Highest abscissa. * @param maxy Highest ordinate. * @param maxz Highest depth. */ def setBounds(minx:Double, miny:Double, minz:Double, maxx:Double, maxy:Double, maxz:Double) = metrics.setBounds(minx, miny, minz, maxx, maxy, maxz) /** Set the graphic graph bounds from the graphic graph. */ def setBounds(graph:GraphicGraph) { setBounds(graph.getMinPos.x, graph.getMinPos.y, 0, graph.getMaxPos.x, graph.getMaxPos.y, 0) } // Utility /** * Set the graph padding. Called in pushView. * @param graph The graphic graph. */ protected def setPadding(graph:GraphicGraph) { padding.copy(graph.getStyle.getPadding) } /** * Process each node to check if it is in the actual view port, and mark invisible nodes. This * method allows for fast node, sprite and edge visibility checking when drawing. This must be * called before each rendering (if the view port changed). Called in pushView. */ protected def checkVisibility(graph:GraphicGraph) { val W:Double = metrics.viewport(0) val H:Double = metrics.viewport(1) nodeInvisible.clear graph.getEachNode.foreach { node:Node => val visible = isNodeIn(node.asInstanceOf[GraphicNode], 0, 0, W, H) && (!node.asInstanceOf[GraphicNode].hidden) && node.asInstanceOf[GraphicNode].positionned; if(! visible) nodeInvisible += node.getId } } protected def paddingXgu:Double = if(padding.units == Units.GU && padding.size > 0) padding.get( 0 ) else 0 protected def paddingYgu:Double = if(padding.units == Units.GU && padding.size > 1) padding.get( 1 ) else paddingXgu protected def paddingXpx:Double = if(padding.units == Units.PX && padding.size > 0) padding.get( 0 ) else 0 protected def paddingYpx:Double = if(padding.units == Units.PX && padding.size > 1) padding.get( 1 ) else paddingXpx /** * Check if a sprite is visible in the current view port. * @param sprite The sprite to check. * @return True if visible. */ protected def isSpriteVisible(sprite:GraphicSprite):Boolean = isSpriteIn(sprite, 0, 0, metrics.viewport(0), metrics.viewport(1)) /** * Check if an edge is visible in the current view port. * @param edge The edge to check. * @return True if visible. */ protected def isEdgeVisible(edge:GraphicEdge):Boolean = { if((!edge.getNode0[GraphicNode].positionned) || (!edge.getNode1[GraphicNode].positionned)) { false } else { val node0Invis = nodeInvisible.contains(edge.getNode0[Node].getId) val node1Invis = nodeInvisible.contains(edge.getNode1[Node].getId) ! (node0Invis && node1Invis) } } /** * Is the given node visible in the given area. * @param node The node to check. * @param X1 The min abscissa of the area. * @param Y1 The min ordinate of the area. * @param X2 The max abscissa of the area. * @param Y2 The max ordinate of the area. * @return True if the node lies in the given area. */ protected def isNodeIn(node:GraphicNode, X1:Double, Y1:Double, X2:Double, Y2:Double):Boolean = { val size = getNodeOrSpriteSize(node)//node.getStyle.getSize val w2 = metrics.lengthToPx(size, 0) / 2 val h2 = if(size.size > 1) metrics.lengthToPx(size, 1)/2 else w2 val src = new Point3(node.getX, node.getY, 0) bck.transform(src) // Tx.transform( src, src ) val x1 = src.x - w2 val x2 = src.x + w2 val y1 = src.y - h2 val y2 = src.y + h2 if( x2 < X1) false else if(y2 < Y1) false else if(x1 > X2) false else if(y1 > Y2) false else true } /** * Is the given sprite visible in the given area. * @param sprite The sprite to check. * @param X1 The min abscissa of the area. * @param Y1 The min ordinate of the area. * @param X2 The max abscissa of the area. * @param Y2 The max ordinate of the area. * @return True if the node lies in the given area. */ protected def isSpriteIn( sprite:GraphicSprite, X1:Double, Y1:Double, X2:Double, Y2:Double ):Boolean = { if( sprite.isAttachedToNode && ( nodeInvisible.contains( sprite.getNodeAttachment.getId ) ) ) { false } else if( sprite.isAttachedToEdge && ! isEdgeVisible( sprite.getEdgeAttachment ) ) { false } else { val size = sprite.getStyle.getSize val w2 = metrics.lengthToPx( size, 0 ) / 2 val h2 = if( size.size > 1 ) metrics.lengthToPx( size, 1 )/2 else w2 val src = spritePositionPx( sprite ) val x1 = src.x - w2 val x2 = src.x + w2 val y1 = src.y - h2 val y2 = src.y + h2 if( x2 < X1 ) false else if( y2 < Y1 ) false else if( x1 > X2 ) false else if( y1 > Y2 ) false else true } } protected def spritePositionPx(sprite:GraphicSprite):Point3 = { getSpritePosition(sprite, new Point3, Units.PX) // sprite.getUnits match { // case Units.PX => { new Point2D.Double( sprite.getX, sprite.getY ) } // case Units.GU => { val pos = new Point2D.Double( sprite.getX, sprite.getY ); Tx.transform( pos, pos ).asInstanceOf[Point2D.Double] } // case Units.PERCENTS => { new Point2D.Double( (sprite.getX/100f)*metrics.viewport.data(0), (sprite.getY/100f)*metrics.viewport.data(1) ) } // } } /** * Check if a node contains the given point (x,y). * @param elt The node. * @param x The point abscissa. * @param y The point ordinate. * @return True if (x,y) is in the given element. */ protected def nodeContains(elt:GraphicElement, x:Double, y:Double):Boolean = { val size = getNodeOrSpriteSize(elt) // elt.getStyle.getSize // TODO use nodeinfo val w2 = metrics.lengthToPx(size, 0) / 2 val h2 = if(size.size() > 1) metrics.lengthToPx(size, 1)/2 else w2 val dst = bck.transform(elt.getX, elt.getY, 0) val x1 = dst.x - w2 val x2 = dst.x + w2 val y1 = dst.y - h2 val y2 = dst.y + h2 if( x < x1) false else if(y < y1) false else if(x > x2) false else if(y > y2) false else true } /** * Check if a sprite contains the given point (x,y). * @param elt The sprite. * @param x The point abscissa. * @param y The point ordinate. * @return True if (x,y) is in the given element. */ protected def spriteContains(elt:GraphicElement, x:Double, y:Double):Boolean = { val sprite = elt.asInstanceOf[GraphicSprite] val size = getNodeOrSpriteSize(elt) //sprite.getStyle.getSize // TODO use nodeinfo val w2 = metrics.lengthToPx(size, 0) / 2 val h2 = if(size.size() > 1) metrics.lengthToPx(size, 1)/2 else w2 val dst = spritePositionPx(sprite) // new Point2D.Double( sprite.getX(), sprite.getY() ) // val dst = new Point2D.Double // // Tx.transform( src, dst ) val x1 = dst.x - w2 val x2 = dst.x + w2 val y1 = dst.y - h2 val y2 = dst.y + h2 if( x < x1) false else if(y < y1) false else if(x > x2) false else if(y > y2) false else true } protected def getNodeOrSpriteSize(elt:GraphicElement):Values = { val info = elt.getAttribute(ElementInfo.attributeName).asInstanceOf[NodeInfo] if(info ne null) { new Values(Units.GU, info.theSize.x, info.theSize.y) } else { elt.getStyle.getSize } } protected def styleVisible(element:GraphicElement):Boolean = { val visibility = element.getStyle.getVisibility element.getStyle.getVisibilityMode match { case VisibilityMode.HIDDEN => false case VisibilityMode.AT_ZOOM => (zoom == visibility(0)) case VisibilityMode.UNDER_ZOOM => (zoom <= visibility(0)) case VisibilityMode.OVER_ZOOM => (zoom >= visibility(0)) case VisibilityMode.ZOOM_RANGE => if(visibility.size > 1) (zoom >= visibility(0) && zoom <= visibility(1)) else true case VisibilityMode.ZOOMS => values.contains(visibility(0)) case _ => true } } def isTextVisible(element:GraphicElement):Boolean = { val visibility = element.getStyle.getTextVisibility element.getStyle.getTextVisibilityMode match { case TextVisibilityMode.HIDDEN => false case TextVisibilityMode.AT_ZOOM => (zoom == visibility(0)) case TextVisibilityMode.UNDER_ZOOM => (zoom <= visibility(0)) case TextVisibilityMode.OVER_ZOOM => (zoom >= visibility(0)) case TextVisibilityMode.ZOOM_RANGE => if(visibility.size > 1) (zoom >= visibility(0) && zoom <= visibility(1)) else true case TextVisibilityMode.ZOOMS => values.contains(visibility(0)) case _ => true } } def getNodeOrSpritePositionGU(elt:GraphicElement, pos:Point3):Point3 = { var p = pos if(p eq null) p = new Point3 elt match { case node:GraphicNode => { p.x = node.getX; p.y = node.getY; p } case sprite:GraphicSprite => { getSpritePosition(sprite, p, Units.GU) } } } /** * Compute the position of a sprite if it is not attached. * @param sprite The sprite. * @param position Where to stored the computed position, if null, the position is created. * @param units The units the computed position must be given into. * @return The same instance as pos, or a new one if pos was null. */ protected def getSpritePositionFree(sprite:GraphicSprite, position:Point3, units:Units):Point3 = { var pos = position if(pos eq null) pos = new Point3 if(sprite.getUnits == units) { pos.x = sprite.getX pos.y = sprite.getY } else if(units == Units.GU && sprite.getUnits == Units.PX) { pos.x = sprite.getX pos.y = sprite.getY bck.transform(pos) } else if(units == Units.PX && sprite.getUnits == Units.GU) { pos.x = sprite.getX pos.y = sprite.getY bck.transform(pos) } else if(units == Units.GU && sprite.getUnits == Units.PERCENTS) { pos.x = metrics.lo.x + (sprite.getX/100f) * metrics.graphWidthGU pos.y = metrics.lo.y + (sprite.getY/100f) * metrics.graphHeightGU } else if(units == Units.PX && sprite.getUnits == Units.PERCENTS) { pos.x = (sprite.getX/100f) * metrics.viewport.data(0) pos.y = (sprite.getY/100f) * metrics.viewport.data(1) } else { throw new RuntimeException("Unhandled yet sprite positioning convertion %s to %s.".format(sprite.getUnits, units)) } pos } /** * Compute the position of a sprite if attached to a node. * @param sprite The sprite. * @param pos Where to stored the computed position, if null, the position is created. * @param units The units the computed position must be given into. * @return The same instance as pos, or a new one if pos was null. */ protected def getSpritePositionNode(sprite:GraphicSprite, position:Point3, units:Units):Point3 = { var pos = position if(pos eq null) pos = new Point3 val node = sprite.getNodeAttachment val radius = metrics.lengthToGu( sprite.getX, sprite.getUnits ) val z = sprite.getZ * (Pi / 180.0) pos.x = node.x + (cos(z) * radius) pos.y = node.y + (sin(z) * radius) if(units == Units.PX) bck.transform(pos) pos } /** * Compute the position of a sprite if attached to an edge. * @param sprite The sprite. * @param pos Where to store the computed position, if null, the position is created. * @param units The units the computed position must be given into. * @return The same instance as pos, or a new one if pos was null. */ protected def getSpritePositionEdge(sprite:GraphicSprite, position:Point3, units:Units):Point3 = { var pos = position if(pos eq null) pos = new Point3 val edge = sprite.getEdgeAttachment.asInstanceOf[GraphicEdge] val info = edge.getAttribute(ElementInfo.attributeName).asInstanceOf[EdgeInfo] if(info ne null) { val o = metrics.lengthToGu(sprite.getY, sprite.getUnits) if(o==0) { val p = info.pointOnShape(sprite.getX) pos.x = p.x pos.y = p.y } else { val p = info.pointOnShapeAndPerpendicular(sprite.getX, o) pos.x = p.x pos.y = p.y } } else { var x = 0.0 var y = 0.0 var dx = 0.0 var dy = 0.0 var d = sprite.getX // Percent on the edge. val o = metrics.lengthToGu(sprite.getY, sprite.getUnits) // Offset from the position given by percent, perpendicular to the edge. x = edge.from.x y = edge.from.y dx = edge.to.x - x dy = edge.to.y - y d = if( d > 1 ) 1 else d d = if( d < 0 ) 0 else d x += dx * d y += dy * d if(o != 0) { d = sqrt( dx*dx + dy*dy ) dx /= d dy /= d x += -dy * o y += dx * o } pos.x = x pos.y = y } if(units == Units.PX) bck.transform(pos) pos } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy