hackedteam/rcs-console

View on GitHub
src/org/un/cava/birdeye/ravis/utils/geom/PoincareProjector.as

Summary

Maintainability
Test Coverage
/* 
 * The MIT License
 *
 * Copyright (c) 2007 The SixDegrees Project Team
 * (Jason Bellone, Juan Rodriguez, Segolene de Basquiat, Daniel Lang).
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.un.cava.birdeye.ravis.utils.geom {

import flash.geom.Point;
import flash.display.DisplayObject;
import org.un.cava.birdeye.ravis.utils.GraphicUtils;
import org.un.cava.birdeye.ravis.graphLayout.layout.Hyperbolic2DLayouter;

    /**
     * This class implements the IProjector, using Poincare model to map 
     * points in Display Geometry (Screen) to those in Non-Euclidean Space 
     * (Hyperbolic Geometry). Other functions that help in animation are
     * also defined.
   * <hr>
     * The implementation is a rewrite of Jens Kanschik's Hypergraph
     * implementation in Java. However, apart from the general idea 
     * and some variable names, this code improves on hypergraph 
     * and also implements additional functionality.
     * 
     * Copyright (C) 2003  Jens Kanschik,
     * mail : jensKanschik@users.sourceforge.net
     * 
     * Copyright of this reimplementation in Flex:
     * (c) Nitin Lamba, 2007.
     * 
     * @author Nitin Lamba
     */
    public class PoincareProjector implements IProjector {
        
        private    var _viewMatrix:IIsometry;
        private    var _inverseViewMatrix:IIsometry;
        private var _model:PoincareModel;
        
        // TEMPORARY VARIABLES - to speed-up computations
        private var t_z:IPoint = new ComplexNumber();
        private var t_z1:ComplexNumber;
        
        private var t_p1:IPoint;
        private var t_p2:IPoint;
        private var t_i:IIsometry;
        /**
         * Constructor of PoincareProjector
         * @param The Poincare model
         * */
        public function PoincareProjector(model:IModel):void {
            _model = model as PoincareModel;
            _inverseViewMatrix = _model.getIdentity();
            _viewMatrix = _model.getIdentity();
        }
        
        /**
         * Returns the arc between two model points mp1 and mp2 
         * as piecewise linear segments.
         * <p><b> NOT USED ANYMORE </b></p>
         * @deprecated
         * @see getCenter(...)
         */
        public function getLineSegments(mp1:IPoint, mp2:IPoint, d:DisplayObject):Array { //Point
            var lineSegments:Array; /*Point*/
            t_z1 = mp1 as ComplexNumber;
            
            _viewMatrix.applyToPoint(mp1);
            _viewMatrix.applyToPoint(mp2);
            if (t_z1.dist(mp2 as ComplexNumber) < 0.1 ) {
                lineSegments = new Array(2); //Point[2];
                lineSegments[0] = map(mp1, d);
                lineSegments[1] = map(mp2, d);
                return lineSegments;
            }

            var n:int = 10;
            if (t_z1.dist(mp2 as ComplexNumber) > 1)
                n = 30;
            lineSegments = new Array(n + 1); /* Point */
            lineSegments[0] = map(t_z1, d);
            t_i = _model.getTranslationPPR(mp1, mp2, 1 / n);
            var i:int;
            for (i = 1; i < n + 1; i++) {
                t_i.applyToPoint(t_z1);
                lineSegments[i] = map(t_z1, d);
            }
            return lineSegments;
        }
        
        /**
         * @inheritDoc
         * */
        public function getCenter(x1:Number, y1:Number,
                                                            x2:Number, y2:Number, d:DisplayObject):Point {
            var rtnX:Number, rtnY:Number;
            var c:Number = Math.min(d.width / 2, d.height / 2);
            var r1:Number = x1 * x1 + y1 * y1 - c * c;
            var r2:Number = x2 * x2 + y2 * y2 - c * c;
            var dr:Number = 2 * ( (x1 - d.width/2) * (y2 - d.height/2) - (x2 - d.width/2) * (y1 - d.height/2) );
            if (dr != 0) {
                rtnX = ( (y2 - d.height/2) * r1 - (y1 - d.height/2) * r2 ) / dr;
                rtnY = - ( (x2 - d.width/2) * r1 - (x1 - d.width/2) * r2 ) / dr;
                return new Point(rtnX, rtnY);
            } else {// a diameter
                return null;
            }
        }
        
        /**
         * Returns a sequence of steps to move a point p (Display) to the centre of 
         * the display object d.
         * 
         * @param p Point to be moved
         * @param d The display object
         * @param oneStep A flag to indicate if the move will be a single step
         * or a series of steps
         * @return An array of steps for the move
         * */
        public function center(p:Point, d:DisplayObject, oneStep:Boolean):Array {
            return move(p, GraphicUtils.getCenter(d), d, oneStep);
        }
        
        /**
         * Returns a sequence of steps to move an IPoint ip (Poincare) to the centre of 
         * the display object d.
         * 
         * @param ip IPoint to be moved
         * @param d The display object
         * @param oneStep A flag to indicate if the move will be a single step
         * or a series of steps
         * @return An array of steps for the move
         * */
        public function centerP(ip:IPoint, d:DisplayObject, oneStep:Boolean):Array {
            return moveP(ip, GraphicUtils.getCenter(d), d, oneStep);
        }
        
        /**
         * Returns a sequence of steps to move an IPoint ip1 (Poincare) to a point p2.
         * 
         * @param ip1 IPoint to be moved
         * @param p2 End point to move to
         * @param d The display object
         * @param oneStep A flag to indicate if the move will be a single step
         * or a series of steps
         * @return An array of steps for the move
         * */
        public function moveP(ip1:IPoint, p2:Point, d:DisplayObject, oneStep:Boolean):Array {
            t_p1 = ip1.clone() as IPoint;
            _viewMatrix.applyToPoint(t_p1);
            t_p2 = inverseMap(p2, d, false);
            if (t_p2 == null)
                return null;
            return moveSteps(t_p1, t_p2, d, oneStep);
        }
        
        /**
         * Returns a sequence of steps to move a Point p1 to a point p2.
         * 
         * @param ip1 IPoint to be moved
         * @param p2 End point to move to
         * @param d The display object
         * @param oneStep A flag to indicate if the move will be a single step
         * or a series of steps
         * @return An array of steps for the move
         * */
        public function move(p1:Point, p2:Point, d:DisplayObject, oneStep:Boolean):Array {
            t_p1 = inverseMap(p1, d, false);
            if (t_p1 == null)
                return null;
            t_p2 = inverseMap(p2, d, false);
            if (t_p2 == null)
                return null;
            return moveSteps(t_p1, t_p2, d, oneStep);
        }
        
        /**
         * @internal
         * */
        private function moveSteps(mp1:IPoint, mp2:IPoint, d:DisplayObject, oneStep:Boolean):Array {
            if (_model.distP(mp1, mp2) < 0.01)
                return null;
            var n:int, i:int;
            if (oneStep)
                n = 1;
            else
                n = Hyperbolic2DLayouter.ANIMATION_STEPS;
            var viewMatrices:Array = new Array(n); //Isometry
            for (i = 0; i < viewMatrices.length; i++) {
                viewMatrices[i] = _model.getTranslationPPR(mp1, mp2, (i + 1) / n as Number);
                (viewMatrices[i] as IIsometry).multiplyRight(getViewMatrix());
            }
            return viewMatrices;
        }
        
        /**
         * @inheritDoc
         * */
        public function project(mp:IPoint, d:DisplayObject):Point {
            t_z.setTo(mp);
            _viewMatrix.applyToPoint(t_z);
            return map(t_z, d);
        }
        
        /**
         * @inheritDoc
         * */
        public function unProject(p:Point, d:DisplayObject, adjustBadNodes:Boolean):IPoint {
            var rtn:IPoint = inverseMap(p, d, adjustBadNodes);
            _inverseViewMatrix.applyToPoint(rtn);
            return rtn;
        }

        /**
         * @internal
         * */
        private function map(mp:IPoint, d:DisplayObject):Point {
            t_z1 = mp as ComplexNumber;
            return new Point(Math.floor((t_z1.real + 1) * 0.5 * d.width), 
                             Math.floor((1 - t_z1.imag) * 0.5 * d.height));
        }
        
        /**
         * @internal
         * */
        private function inverseMap(p:Point, d:DisplayObject, adjustBadNodes:Boolean):IPoint {
            var z:ComplexNumber = new ComplexNumber();
            if (d.width != 0)
                z.real = p.x * 2.0 / d.width - 1;
            if (d.height != 0)
                z.imag = 1 - p.y * 2.0 / d.height;
            if (z.norm2() >= 1.0) {
                //LogUtil.warn(_LOG, "Point " + p + " out of bounds");
                if (adjustBadNodes) {// Adjusting bad node to a valid node at the same orientation
                    var angle:Number = Math.atan(Math.abs(z.imag / z.real));
                    z.real = 0.8 * Math.cos(angle) * z.real / Math.abs(z.real) ;
                    z.imag = 0.8 * Math.sin(angle) * z.imag / Math.abs(z.imag) ;
                } else {
                    return null;
                }
            }
            return z;
        }
        
        /**
         * @inheritDoc
         * */
        public function getScale(mp:IPoint):Point {
            t_z.setTo(mp);
            _viewMatrix.applyToPoint(t_z);
            var scale:Number = 1 - (t_z as ComplexNumber).norm2();
            return new Point(scale, scale);
        }
        
        /**
         * @inheritDoc
         * */
        public function getInverseViewMatrix():IIsometry {
            return _inverseViewMatrix;
        }
        
        /**
         * @inheritDoc
         * */
        public function getViewMatrix():IIsometry {
            return _viewMatrix;
        }
        
        /**
         * @inheritDoc
         * */
        public function setViewMatrix(vm:IIsometry):void {
            if (vm != null) {
                _viewMatrix = vm;
                _inverseViewMatrix = _viewMatrix.getInverse();
            } else {
                _viewMatrix = _model.getIdentity();
                _inverseViewMatrix = _model.getIdentity();
            }
        }
    }
}