| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package mx.utils |
| { |
| |
| import flash.display.DisplayObject; |
| import flash.geom.Matrix; |
| import flash.geom.Matrix3D; |
| import flash.geom.PerspectiveProjection; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| import flash.geom.Utils3D; |
| import flash.geom.Vector3D; |
| import flash.system.ApplicationDomain; |
| |
| import mx.core.mx_internal; |
| |
| use namespace mx_internal; |
| |
| [ExcludeClass] |
| |
| /** |
| * @private |
| * The MatrixUtil class is for internal use only. |
| * Class for matrix and geometric related math routines. |
| */ |
| public final class MatrixUtil |
| { |
| include "../core/Version.as"; |
| |
| private static const RADIANS_PER_DEGREES:Number = Math.PI / 180; |
| mx_internal static var SOLUTION_TOLERANCE:Number = 0.1; |
| mx_internal static var MIN_MAX_TOLERANCE:Number = 0.1; |
| |
| private static var staticPoint:Point = new Point(); |
| |
| // For use in getConcatenatedMatrix function |
| private static var fakeDollarParent:QName; |
| private static var uiComponentClass:Class; |
| private static var uiMovieClipClass:Class; |
| private static var usesMarshalling:Object; |
| private static var lastModuleFactory:Object; |
| private static var computedMatrixProperty:QName; |
| private static var $transformProperty:QName; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Returns rotation value clamped between -180 and 180 degreeds. |
| * This mimicks the Flash player behavior. |
| */ |
| public static function clampRotation(value:Number):Number |
| { |
| // Flash player doesn't handle values larger than 2^15 - 1 (FP-749). |
| if (value > 180 || value < -180) |
| { |
| value = value % 360; |
| |
| if (value > 180) |
| value = value - 360; |
| else if (value < -180) |
| value = value + 360; |
| } |
| return value; |
| } |
| |
| /** |
| * Returns a static Point object with the result. |
| * If matrix is null, point is untransformed. |
| */ |
| public static function transformPoint(x:Number, y:Number, m:Matrix):Point |
| { |
| if (!m) |
| { |
| staticPoint.x = x; |
| staticPoint.y = y; |
| return staticPoint; |
| } |
| |
| staticPoint.x = m.a * x + m.c * y + m.tx; |
| staticPoint.y = m.b * x + m.d * y + m.ty; |
| return staticPoint; |
| } |
| |
| public static function composeMatrix(x:Number = 0, |
| y:Number = 0, |
| scaleX:Number = 1, |
| scaleY:Number = 1, |
| rotation:Number = 0, |
| transformX:Number = 0, |
| transformY:Number = 0):Matrix |
| { |
| var m:Matrix = new Matrix(); |
| m.translate(-transformX, -transformY); |
| m.scale(scaleX, scaleY); |
| if (rotation != 0) |
| m.rotate(rotation / 180 * Math.PI); |
| m.translate(transformX + x, transformY + y); |
| return m; |
| } |
| |
| /** |
| * Decompose a matrix into its component scale, rotation, and translation parts. |
| * The Vector of Numbers passed in the components parameter will be |
| * populated by this function with the component parts. |
| * |
| * @param components Vector which holds the component scale, rotation |
| * and translation values. |
| * x = components[0] |
| * y = components[1] |
| * rotation = components[2] |
| * scaleX = components[3] |
| * scaleY = components[4] |
| * |
| * @param matrix The matrix to decompose |
| * @param transformX The x value of the transform center |
| * @param transformY The y value of the transform center |
| */ |
| public static function decomposeMatrix(components:Vector.<Number>, |
| matrix:Matrix, |
| transformX:Number = 0, |
| transformY:Number = 0):void |
| { |
| // else decompose matrix. Don't use MatrixDecompose(), it can return erronous values |
| // when negative scales (and therefore skews) are in use. |
| var Ux:Number; |
| var Uy:Number; |
| var Vx:Number; |
| var Vy:Number; |
| |
| Ux = matrix.a; |
| Uy = matrix.b; |
| components[3] = Math.sqrt(Ux*Ux + Uy*Uy); |
| |
| Vx = matrix.c; |
| Vy = matrix.d; |
| components[4] = Math.sqrt(Vx*Vx + Vy*Vy ); |
| |
| // sign of the matrix determinant will tell us if the space is inverted by a 180 degree skew or not. |
| var determinant:Number = Ux*Vy - Uy*Vx; |
| if (determinant < 0) // if so, choose y-axis scale as the skewed one. Unfortunately, its impossible to tell if it originally was the y or x axis that had the negative scale/skew. |
| { |
| components[4] = -(components[4]); |
| Vx = -Vx; |
| Vy = -Vy; |
| } |
| |
| components[2] = Math.atan2( Uy, Ux ) / RADIANS_PER_DEGREES; |
| |
| if (transformX != 0 || transformY != 0) |
| { |
| var postTransformCenter:Point = matrix.transformPoint(new Point(transformX,transformY)); |
| components[0] = postTransformCenter.x - transformX; |
| components[1] = postTransformCenter.y - transformY; |
| } |
| else |
| { |
| components[0] = matrix.tx; |
| components[1] = matrix.ty; |
| } |
| } |
| |
| /** |
| * @return Returns the union of <code>rect</code> and |
| * <code>Rectangle(left, top, right - left, bottom - top)</code>. |
| * Note that if rect is non-null, it will be updated to reflect the return value. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function rectUnion(left:Number, top:Number, right:Number, bottom:Number, |
| rect:Rectangle):Rectangle |
| { |
| if (!rect) |
| return new Rectangle(left, top, right - left, bottom - top); |
| |
| var minX:Number = Math.min(rect.left, left); |
| var minY:Number = Math.min(rect.top, top); |
| var maxX:Number = Math.max(rect.right, right); |
| var maxY:Number = Math.max(rect.bottom, bottom); |
| |
| rect.x = minX; |
| rect.y = minY; |
| rect.width = maxX - minX; |
| rect.height = maxY - minY; |
| return rect; |
| } |
| |
| /** |
| * Calculates the bounding box of a post-transformed ellipse. |
| * |
| * @param cx The x coordinate of the ellipse's center |
| * @param cy The y coordinate of the ellipse's center |
| * @param rx The horizontal radius of the ellipse |
| * @param ry The vertical radius of the ellipse |
| * @param matrix The transformation matrix. |
| * @param rect If non-null, rect will be updated to the union of rect and |
| * the segment bounding box. |
| * @return Returns the union of the passed in rect with the |
| * bounding box of the the post-transformed ellipse. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function getEllipseBoundingBox(cx:Number, cy:Number, |
| rx:Number, ry:Number, |
| matrix:Matrix, |
| rect:Rectangle = null):Rectangle |
| { |
| var a:Number = matrix.a; |
| var b:Number = matrix.b; |
| var c:Number = matrix.c; |
| var d:Number = matrix.d; |
| |
| // Ellipse can be represented by the following parametric equations: |
| // |
| // (1) x = cx + rx * cos(t) |
| // (2) y = cy + ry * sin(t) |
| // |
| // After applying transformation with matrix m(a, c, b, d) we get: |
| // |
| // (3) x = a * cx + a * cos(t) * rx + c * cy + c * sin(t) * ry + m.tx |
| // (4) y = b * cx + b * cos(t) * rx + d * cy + d * sin(t) * ry + m.ty |
| // |
| // In (3) and (4) x and y are functions of a parameter t. To find the extremums we need |
| // to find where dx/dt and dy/dt reach zero: |
| // |
| // (5) dx/dt = - a * sin(t) * rx + c * cos(t) * ry |
| // (6) dy/dt = - b * sin(t) * rx + d * cos(t) * ry |
| // (7) dx/dt = 0 <=> sin(t) / cos(t) = (c * ry) / (a * rx); |
| // (8) dy/dt = 0 <=> sin(t) / cos(t) = (d * ry) / (b * rx); |
| |
| if (rx == 0 && ry == 0) |
| { |
| var pt:Point = new Point(cx, cy); |
| pt = matrix.transformPoint(pt); |
| return rectUnion(pt.x, pt.y, pt.x, pt.y, rect); |
| } |
| |
| var t:Number; |
| var t1:Number; |
| |
| if (a * rx == 0) |
| t = Math.PI / 2; |
| else |
| t = Math.atan((c * ry) / (a * rx)); |
| |
| if (b * rx == 0) |
| t1 = Math.PI / 2; |
| else |
| t1 = Math.atan((d * ry) / (b * rx)); |
| |
| var x1:Number = a * Math.cos(t) * rx + c * Math.sin(t) * ry; |
| var x2:Number = -x1; |
| x1 += a * cx + c * cy + matrix.tx; |
| x2 += a * cx + c * cy + matrix.tx; |
| |
| var y1:Number = b * Math.cos(t1) * rx + d * Math.sin(t1) * ry; |
| var y2:Number = -y1; |
| y1 += b * cx + d * cy + matrix.ty; |
| y2 += b * cx + d * cy + matrix.ty; |
| |
| return rectUnion(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2), rect); |
| } |
| |
| /** |
| * @param x0 x coordinate of the first control point |
| * @param y0 y coordinate of the first control point |
| * @param x1 x coordinate of the second control point |
| * @param y1 y coordinate of the second control point |
| * @param x2 x coordinate of the third control point |
| * @param y2 y coordinate of the third control point |
| * @param sx The pre-transform scale factor for x coordinates. |
| * @param sy The pre-transform scale factor for y coordinates. |
| * @param matrix The transformation matrix. Can be null for identity transformation. |
| * @param rect If non-null, rect will be updated to the union of rect and |
| * the segment bounding box. |
| * @return Returns the union of the post-transformed quadratic |
| * bezier segment's axis aligned bounding box and the passed in rect. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| static public function getQBezierSegmentBBox(x0:Number, y0:Number, |
| x1:Number, y1:Number, |
| x2:Number, y2:Number, |
| sx:Number, sy:Number, |
| matrix:Matrix, |
| rect:Rectangle):Rectangle |
| { |
| var pt:Point; |
| pt = MatrixUtil.transformPoint(x0 * sx, y0 * sy, matrix); |
| x0 = pt.x; |
| y0 = pt.y; |
| |
| pt = MatrixUtil.transformPoint(x1 * sx, y1 * sy, matrix); |
| x1 = pt.x; |
| y1 = pt.y; |
| |
| pt = MatrixUtil.transformPoint(x2 * sx, y2 * sy, matrix); |
| x2 = pt.x; |
| y2 = pt.y; |
| |
| var minX:Number = Math.min(x0, x2); |
| var maxX:Number = Math.max(x0, x2); |
| |
| var minY:Number = Math.min(y0, y2); |
| var maxY:Number = Math.max(y0, y2); |
| |
| var txDiv:Number = x0 - 2 * x1 + x2; |
| if (txDiv != 0) |
| { |
| var tx:Number = (x0 - x1) / txDiv; |
| if (0 <= tx && tx <= 1) |
| { |
| var x:Number = (1 - tx) * (1 - tx) * x0 + 2 * tx * (1 - tx) * x1 + tx * tx * x2; |
| minX = Math.min(x, minX); |
| maxX = Math.max(x, maxX); |
| } |
| } |
| |
| var tyDiv:Number = y0 - 2 * y1 + y2; |
| if (tyDiv != 0) |
| { |
| var ty:Number = (y0 - y1) / tyDiv; |
| if (0 <= ty && ty <= 1) |
| { |
| var y:Number = (1 - ty) * (1 - ty) * y0 + 2 * ty * (1 - ty) * y1 + ty * ty * y2; |
| minY = Math.min(y, minY); |
| maxY = Math.max(y, maxY); |
| } |
| } |
| |
| return rectUnion(minX, minY, maxX, maxY, rect); |
| } |
| |
| /** |
| * @param width The width of the bounds to be transformed. |
| * @param height The height of the bounds to be transformed. |
| * @param matrix The transfomration matrix. |
| * |
| * @param vec If vec is non-null it will be set to the vector from the |
| * transformed bounds top left to the untransformed bounds top left |
| * in the coordinate space defined by <code>matrix</code>. |
| * This is useful if you want to align the transformed bounds to x,y |
| * by modifying the object's position. Moving the object by |
| * <code>x + vec.x</code> and <code>y + vec.y</code> respectively |
| * will offset the transformed bounds top left corner by x,y. |
| * |
| * @return Returns the transformed bounds. Note that the Point object returned will be reused |
| * by other MatrixUtil methods. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function transformSize(width:Number, height:Number, matrix:Matrix):Point |
| { |
| const a:Number = matrix.a; |
| const b:Number = matrix.b; |
| const c:Number = matrix.c; |
| const d:Number = matrix.d; |
| |
| // transform point (0,0) |
| var x1:Number = 0; |
| var y1:Number = 0; |
| |
| // transform point (width, 0) |
| var x2:Number = width * a; |
| var y2:Number = width * b; |
| |
| // transform point (0, height) |
| var x3:Number = height * c; |
| var y3:Number = height * d; |
| |
| // transform point (width, height) |
| var x4:Number = x2 + x3; |
| var y4:Number = y2 + y3; |
| |
| var minX:Number = Math.min(Math.min(x1, x2), Math.min(x3, x4)); |
| var maxX:Number = Math.max(Math.max(x1, x2), Math.max(x3, x4)); |
| var minY:Number = Math.min(Math.min(y1, y2), Math.min(y3, y4)); |
| var maxY:Number = Math.max(Math.max(y1, y2), Math.max(y3, y4)); |
| |
| staticPoint.x = maxX - minX; |
| staticPoint.y = maxY - minY; |
| return staticPoint; |
| } |
| |
| /** |
| * @param width The width of the bounds to be transformed. |
| * @param height The height of the bounds to be transformed. |
| * @param matrix The transfomration matrix. |
| * |
| * @param topleft If topLeft is non-null it will be used as the origin of the bounds |
| * rectangle to be transformed. On return, it will be set to the top left of the rectangle |
| * after transformation. |
| * |
| * @return Returns the transformed width and height. Note that the Point object returned will be reused |
| * by other MatrixUtil methods. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function transformBounds(width:Number, height:Number, matrix:Matrix, topLeft:Point = null):Point |
| { |
| const a:Number = matrix.a; |
| const b:Number = matrix.b; |
| const c:Number = matrix.c; |
| const d:Number = matrix.d; |
| |
| // transform point (0,0) |
| var x1:Number = 0; |
| var y1:Number = 0; |
| |
| // transform point (width, 0) |
| var x2:Number = width * a; |
| var y2:Number = width * b; |
| |
| // transform point (0, height) |
| var x3:Number = height * c; |
| var y3:Number = height * d; |
| |
| // transform point (width, height) |
| var x4:Number = x2 + x3; |
| var y4:Number = y2 + y3; |
| |
| var minX:Number = Math.min(Math.min(x1, x2), Math.min(x3, x4)); |
| var maxX:Number = Math.max(Math.max(x1, x2), Math.max(x3, x4)); |
| var minY:Number = Math.min(Math.min(y1, y2), Math.min(y3, y4)); |
| var maxY:Number = Math.max(Math.max(y1, y2), Math.max(y3, y4)); |
| |
| staticPoint.x = maxX - minX; |
| staticPoint.y = maxY - minY; |
| |
| if (topLeft) |
| { |
| const tx:Number = matrix.tx; |
| const ty:Number = matrix.ty; |
| const x:Number = topLeft.x; |
| const y:Number = topLeft.y; |
| |
| topLeft.x = minX + a * x + b * y + tx; |
| topLeft.y = minY + c * x + d * y + ty; |
| } |
| return staticPoint; |
| } |
| |
| /** |
| * Returns the axis aligned bounding box <code>bounds</code> transformed |
| * with <code>matrix</code> and then projected with <code>projection</code>. |
| * |
| * @param bounds The bounds, in child coordinates, to be transformed and projected. |
| * @param matrix <p>The transformation matrix. Note that the method will clobber the |
| * original matrix values.</p> |
| * @param projection The projection. |
| * @return Returns the <code>bounds</code> parameter that has been updated with the |
| * transformed and projected bounds. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function projectBounds(bounds:Rectangle, |
| matrix:Matrix3D, |
| projection:PerspectiveProjection):Rectangle |
| { |
| // Setup the matrix |
| var centerX:Number = projection.projectionCenter.x; |
| var centerY:Number = projection.projectionCenter.y; |
| matrix.appendTranslation(-centerX, -centerY, projection.focalLength); |
| matrix.append(projection.toMatrix3D()); |
| |
| // Project the corner points |
| var pt1:Vector3D = new Vector3D(bounds.left, bounds.top, 0); |
| var pt2:Vector3D = new Vector3D(bounds.right, bounds.top, 0) |
| var pt3:Vector3D = new Vector3D(bounds.left, bounds.bottom, 0); |
| var pt4:Vector3D = new Vector3D(bounds.right, bounds.bottom, 0); |
| pt1 = Utils3D.projectVector(matrix, pt1); |
| pt2 = Utils3D.projectVector(matrix, pt2); |
| pt3 = Utils3D.projectVector(matrix, pt3); |
| pt4 = Utils3D.projectVector(matrix, pt4); |
| |
| // Find the bounding box in 2D |
| var maxX:Number = Math.max(Math.max(pt1.x, pt2.x), Math.max(pt3.x, pt4.x)); |
| var minX:Number = Math.min(Math.min(pt1.x, pt2.x), Math.min(pt3.x, pt4.x)); |
| var maxY:Number = Math.max(Math.max(pt1.y, pt2.y), Math.max(pt3.y, pt4.y)); |
| var minY:Number = Math.min(Math.min(pt1.y, pt2.y), Math.min(pt3.y, pt4.y)); |
| |
| // Add back the projection center |
| bounds.x = minX + centerX; |
| bounds.y = minY + centerY; |
| bounds.width = maxX - minX; |
| bounds.height = maxY - minY; |
| return bounds; |
| } |
| |
| /** |
| * @param matrix |
| * @return Returns true when <code>pt == matrix.DeltaTransformPoint(pt)</code> |
| * for any <code>pt:Point</code> (<code>matrix</code> is identity matrix, |
| * when disregarding the translation part). |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function isDeltaIdentity(matrix:Matrix):Boolean |
| { |
| return (matrix.a == 1 && matrix.d == 1 && |
| matrix.b == 0 && matrix.c == 0); |
| } |
| |
| /** |
| * <code>fitBounds</code> Calculates a size (x,y) for a bounding box (0,0,x,y) |
| * such that the bounding box transformed with <code>matrix</code> will fit |
| * into (0,0,width,height). |
| * |
| * @param width This is the width of the bounding box that calculated size |
| * needs to fit in. |
| * |
| * @param height This is the height of the bounding box that the calculated |
| * size needs to fit in. |
| * |
| * @param matrix This defines the transformations that the function will take |
| * into account when calculating the size. The bounding box (0,0,x,y) of the |
| * calculated size (x,y) transformed with <code>matrix</code> will fit in the |
| * specified <code>width</code> and <code>height</code>. |
| * |
| * @param explicitWidth Explicit width for the calculated size. The function |
| * will first try to find a solution using this width. |
| * |
| * @param explicitHeight Preferred height for the calculated size. The function |
| * will first try to find a solution using this height. |
| * |
| * @param preferredWidth Preferred width for the calculated size. If possible |
| * the function will set the calculated size width to this value. |
| * |
| * @param preferredHeight Preferred height for the calculated size. If possible |
| * the function will set the calculated size height to this value. |
| * |
| * @param minWidth The minimum allowed value for the calculated size width. |
| * |
| * @param minHeight The minimum allowed value for the calculated size height. |
| * |
| * @param maxWidth The maximum allowed value for the calculated size width. |
| * |
| * @param maxHeight The maximum allowed value for the calculated size height. |
| * |
| * @return Returns the size (x,y) such that the bounding box (0,0,x,y) will |
| * fit into (0,0,width,height) after transformation with <code>matrix</code>. |
| * Returns null if there is no possible solution. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function fitBounds(width:Number, height:Number, matrix:Matrix, |
| explicitWidth:Number, explicitHeight:Number, |
| preferredWidth:Number, preferredHeight:Number, |
| minWidth:Number, minHeight:Number, |
| maxWidth:Number, maxHeight:Number):Point |
| { |
| if (isNaN(width) && isNaN(height)) |
| return new Point(preferredWidth, preferredHeight); |
| |
| // Allow for precision errors by including tolerance for certain values. |
| const newMinWidth:Number = (minWidth < MIN_MAX_TOLERANCE) ? 0 : minWidth - MIN_MAX_TOLERANCE; |
| const newMinHeight:Number = (minHeight < MIN_MAX_TOLERANCE) ? 0 : minHeight - MIN_MAX_TOLERANCE; |
| const newMaxWidth:Number = maxWidth + MIN_MAX_TOLERANCE; |
| const newMaxHeight:Number = maxHeight + MIN_MAX_TOLERANCE; |
| |
| var actualSize:Point; |
| |
| if (!isNaN(width) && !isNaN(height)) |
| { |
| actualSize = calcUBoundsToFitTBounds(width, height, matrix, |
| newMinWidth, newMinHeight, |
| newMaxWidth, newMaxHeight); |
| |
| // If we couldn't fit in both dimensions, try to fit only one and |
| // don't stick out of the other |
| if (!actualSize) |
| { |
| var actualSize1:Point; |
| actualSize1 = fitTBoundsWidth(width, matrix, |
| explicitWidth, explicitHeight, |
| preferredWidth, preferredHeight, |
| newMinWidth, newMinHeight, |
| newMaxWidth, newMaxHeight); |
| |
| // If we fit the width, but not the height. |
| if (actualSize1) |
| { |
| var fitHeight:Number = transformSize(actualSize1.x, actualSize1.y, matrix).y; |
| if (fitHeight - SOLUTION_TOLERANCE > height) |
| actualSize1 = null; |
| } |
| |
| var actualSize2:Point |
| actualSize2 = fitTBoundsHeight(height, matrix, |
| explicitWidth, explicitHeight, |
| preferredWidth, preferredHeight, |
| newMinWidth, newMinHeight, |
| newMaxWidth, newMaxHeight); |
| |
| // If we fit the height, but not the width |
| if (actualSize2) |
| { |
| var fitWidth:Number = transformSize(actualSize2.x, actualSize2.y, matrix).x; |
| if (fitWidth - SOLUTION_TOLERANCE > width) |
| actualSize2 = null; |
| } |
| |
| if (actualSize1 && actualSize2) |
| { |
| // Pick a solution |
| actualSize = ((actualSize1.x * actualSize1.y) > (actualSize2.x * actualSize2.y)) ? actualSize1 : actualSize2; |
| } |
| else if (actualSize1) |
| { |
| actualSize = actualSize1; |
| } |
| else |
| { |
| actualSize = actualSize2; |
| } |
| } |
| return actualSize; |
| } |
| else if (!isNaN(width)) |
| { |
| return fitTBoundsWidth(width, matrix, |
| explicitWidth, explicitHeight, |
| preferredWidth, preferredHeight, |
| newMinWidth, newMinHeight, |
| newMaxWidth, newMaxHeight); |
| } |
| else |
| { |
| return fitTBoundsHeight(height, matrix, |
| explicitWidth, explicitHeight, |
| preferredWidth, preferredHeight, |
| newMinWidth, newMinHeight, |
| newMaxWidth, newMaxHeight); |
| } |
| } |
| |
| /** |
| * @private |
| * |
| * <code>fitTBoundsWidth</code> Calculates a size (x,y) for a bounding box (0,0,x,y) |
| * such that the bounding box transformed with <code>matrix</code> will fit |
| * into the specified width. |
| * |
| * @param width This is the width of the bounding box that calculated size |
| * needs to fit in. |
| * |
| * @param matrix This defines the transformations that the function will take |
| * into account when calculating the size. The bounding box (0,0,x,y) of the |
| * calculated size (x,y) transformed with <code>matrix</code> will fit in the |
| * specified <code>width</code> and <code>height</code>. |
| * |
| * @param explicitWidth Explicit width for the calculated size. The function |
| * will first try to find a solution using this width. |
| * |
| * @param explicitHeight Preferred height for the calculated size. The function |
| * will first try to find a solution using this height. |
| * |
| * @param preferredWidth Preferred width for the calculated size. If possible |
| * the function will set the calculated size width to this value. |
| * |
| * @param preferredHeight Preferred height for the calculated size. If possible |
| * the function will set the calculated size height to this value. |
| * |
| * @param minWidth The minimum allowed value for the calculated size width. |
| * |
| * @param minHeight The minimum allowed value for the calculated size height. |
| * |
| * @param maxWidth The maximum allowed value for the calculated size width. |
| * |
| * @param maxHeight The maximum allowed value for the calculated size height. |
| * |
| * @return Returns the size (x,y) such that the bounding box (0,0,x,y) will |
| * fit into (0,0,width,height) after transformation with <code>matrix</code>. |
| * Returns null if there is no possible solution. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private static function fitTBoundsWidth(width:Number, matrix:Matrix, |
| explicitWidth:Number, explicitHeight:Number, |
| preferredWidth:Number, preferredHeight:Number, |
| minWidth:Number, minHeight:Number, |
| maxWidth:Number, maxHeight:Number):Point |
| { |
| var actualSize:Point; |
| |
| // cases 1 and 2: only explicit width or explicit height is specified, |
| // so we try to find a solution with that hard constraint. |
| if (!isNaN(explicitWidth) && isNaN(explicitHeight)) |
| { |
| actualSize = calcUBoundsToFitTBoundsWidth(width, matrix, |
| explicitWidth, preferredHeight, |
| explicitWidth, minHeight, |
| explicitWidth, maxHeight); |
| |
| if (actualSize) |
| return actualSize; |
| } |
| else if (isNaN(explicitWidth) && !isNaN(explicitHeight)) |
| { |
| actualSize = calcUBoundsToFitTBoundsWidth(width, matrix, |
| preferredWidth, explicitHeight, |
| minWidth, explicitHeight, |
| maxWidth, explicitHeight); |
| if (actualSize) |
| return actualSize; |
| } |
| |
| // case 3: default case. When explicitWidth, explicitHeight are both set |
| // or not set, we use the preferred size since calcUBoundsToFitTBoundsWidth |
| // will just pick one. |
| actualSize = calcUBoundsToFitTBoundsWidth(width, matrix, |
| preferredWidth, preferredHeight, |
| minWidth, minHeight, |
| maxWidth, maxHeight); |
| |
| return actualSize; |
| } |
| |
| /** |
| * @private |
| * |
| * <code>fitTBoundsWidth</code> Calculates a size (x,y) for a bounding box (0,0,x,y) |
| * such that the bounding box transformed with <code>matrix</code> will fit |
| * into the specified height. |
| * |
| * @param height This is the height of the bounding box that the calculated |
| * size needs to fit in. |
| * |
| * @param matrix This defines the transformations that the function will take |
| * into account when calculating the size. The bounding box (0,0,x,y) of the |
| * calculated size (x,y) transformed with <code>matrix</code> will fit in the |
| * specified <code>width</code> and <code>height</code>. |
| * |
| * @param explicitWidth Explicit width for the calculated size. The function |
| * will first try to find a solution using this width. |
| * |
| * @param explicitHeight Preferred height for the calculated size. The function |
| * will first try to find a solution using this height. |
| * |
| * @param preferredWidth Preferred width for the calculated size. If possible |
| * the function will set the calculated size width to this value. |
| * |
| * @param preferredHeight Preferred height for the calculated size. If possible |
| * the function will set the calculated size height to this value. |
| * |
| * @param minWidth The minimum allowed value for the calculated size width. |
| * |
| * @param minHeight The minimum allowed value for the calculated size height. |
| * |
| * @param maxWidth The maximum allowed value for the calculated size width. |
| * |
| * @param maxHeight The maximum allowed value for the calculated size height. |
| * |
| * @return Returns the size (x,y) such that the bounding box (0,0,x,y) will |
| * fit into (0,0,width,height) after transformation with <code>matrix</code>. |
| * Returns null if there is no possible solution. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private static function fitTBoundsHeight(height:Number, matrix:Matrix, |
| explicitWidth:Number, explicitHeight:Number, |
| preferredWidth:Number, preferredHeight:Number, |
| minWidth:Number, minHeight:Number, |
| maxWidth:Number, maxHeight:Number):Point |
| { |
| var actualSize:Point; |
| |
| // cases 1 and 2: only explicit width or explicit height is specified, |
| // so we try to find a solution with that hard constraint. |
| if (!isNaN(explicitWidth) && isNaN(explicitHeight)) |
| { |
| actualSize = calcUBoundsToFitTBoundsHeight(height, matrix, |
| explicitWidth, preferredHeight, |
| explicitWidth, minHeight, |
| explicitWidth, maxHeight); |
| |
| if (actualSize) |
| return actualSize; |
| } |
| else if (isNaN(explicitWidth) && !isNaN(explicitHeight)) |
| { |
| actualSize = calcUBoundsToFitTBoundsHeight(height, matrix, |
| preferredWidth, explicitHeight, |
| minWidth, explicitHeight, |
| maxWidth, explicitHeight); |
| if (actualSize) |
| return actualSize; |
| } |
| |
| // case 3: default case. When explicitWidth, explicitHeight are both set |
| // or not set, we use the preferred size since calcUBoundsToFitTBoundsWidth |
| // will just pick one. |
| actualSize = calcUBoundsToFitTBoundsHeight(height, matrix, |
| preferredWidth, preferredHeight, |
| minWidth, minHeight, |
| maxWidth, maxHeight); |
| |
| return actualSize; |
| } |
| |
| /** |
| * Calculates (x,y) such that the bounding box (0,0,x,y) transformed |
| * with <code>matrix</code> will have bounding box with |
| * height equal to <code>h</code>. |
| * x and y are restricted by <code>minX</code>, <code>maxX</code> and |
| * <code>minY</code>, <code>maxY</code>. |
| * |
| * If possible x will be set to <code>preferredX</code> or |
| * y will be set to <code>preferredY</code>. |
| * |
| * When there are multiple solutions, the function picks the one that |
| * minimizes the bounding box area of transformed (0,0,x,y). |
| * |
| * The functon assumes <code>minX >= 0</code> and <code>minY >= 0</code> |
| * (solution components x and y are non-negative). |
| * |
| * @return Returns Point(x,y) or null if no solution exists. |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| static public function calcUBoundsToFitTBoundsHeight(h:Number, |
| matrix:Matrix, |
| preferredX:Number, |
| preferredY:Number, |
| minX:Number, |
| minY:Number, |
| maxX:Number, |
| maxY:Number):Point |
| { |
| // Untransformed bounds size is (x,y). The corners of the untransformed |
| // bounding box are p1(0,0) p2(x,0) p3(0,y) p4(x,y). |
| // Matrix is | a c tx | |
| // | b d ty | |
| // |
| // After transfomation with the matrix those four points are: |
| // t1 = (0, 0) = matrix.deltaTransformPoint(p1) |
| // t2 = (ax, bx) = matrix.deltaTransformPoint(p2) |
| // t3 = (cy, dy) = matrix.deltaTransformPoint(p3) |
| // t4 = (ax + cy, cx + dy) = matrix.deltaTransformPoint(p4) |
| // |
| // The transformed bounds bounding box dimensions are (w,h): |
| // (1) w = max( t1.x, t2.x, t3.x, t4.x ) - min( t1.x, t2.x, t3.x, t4.x) |
| // (2) h = max( t1.y, t2.y, t3.y, t4.y ) - min( t1.y, t2.y, t3.y, t4.y) |
| // |
| // Looking at all the possible cases for min and max functions above, |
| // we can construct and solve simple linear systems for x and y. |
| // For example in the case of |
| // t1.x <= t2.x <= t3.x <= t4.x |
| // our first equation is |
| // (1) w = t4.x - t1.x <==> w = ax + cy |
| // |
| // To minimize the cases we're looking at we can take advantage of |
| // the limits we have: x >= 0, y >= 0; |
| // Taking into account these limits we deduce that: |
| // a*c >= 0 gives us (1) w = abs( t4.x - t1.x ) = abs( ax + cy ) |
| // a*c < 0 gives us (1) w = abs( t2.x - t3.x ) = abs( ax - cy ) |
| // b*d >= 0 gives us (2) h = abs( t4.y - t1.y ) = abs( bx + dy ) |
| // b*d < 0 gives us (2) h = abs( t2.y - t3.y ) = abs( bx - dy ) |
| // |
| // If we do a substitution such that |
| // c1 = a*c >= 0 ? c : -c |
| // d1 = b*d >= 0 ? d : -d |
| // we get the following linear system: |
| // (1) w = abs( ax + c1y ) |
| // (2) h = abs( bx + d1y ) |
| // |
| // Since we're matching height we only care about (2) |
| |
| var b:Number = matrix.b; |
| var d:Number = matrix.d; |
| |
| // If components are very close to zero, zero them out to handle the special cases |
| if (-1.0e-9 < b && b < +1.0e-9) |
| b = 0; |
| if (-1.0e-9 < d && d < +1.0e-9) |
| d = 0; |
| |
| if (b == 0 && d == 0) |
| return null; // No solution |
| |
| // Handle special cases first |
| if (b == 0 && d == 0) |
| return null; // No solution |
| |
| if (b == 0) |
| return new Point( preferredX, h / Math.abs(d) ); |
| else if (d == 0) |
| return new Point( h / Math.abs(b), preferredY ); |
| |
| const d1:Number = (b*d >= 0) ? d : -d; |
| // Now we have the following linear sytesm: |
| // (1) x = preferredX or y = preferredY |
| // (2) h = abs( bx + d1y ) |
| |
| var s:Point; |
| var x:Number; |
| var y:Number; |
| |
| if (d1 != 0 && preferredX > 0) |
| { |
| const invD1:Number = 1 / d1; |
| preferredX = Math.max(minX, Math.min(maxX, preferredX)); |
| x = preferredX; |
| |
| // Case1: |
| // bx + d1y >= 0 |
| // x = preferredX |
| y = (h - b * x) * invD1; |
| if (minY <= y && y <= maxY && |
| b * x + d1 * y >= 0 ) // Satisfy Case1 |
| { |
| s = new Point(x, y); |
| } |
| |
| // Case2: |
| // bx + d1y < 0 |
| // x = preferredX |
| y = (-h - b * x) * invD1; |
| if (minY <= y && y <= maxY && |
| b * x + d1 * y < 0 ) // Satisfy Case2 |
| { |
| // If there is no solution, or the new solution yields smaller value, pick the new solution. |
| if (!s || transformSize(s.x, s.y, matrix).x > transformSize(x, y, matrix).x) |
| s = new Point(x, y); |
| } |
| } |
| |
| if (b != 0 && preferredY > 0) |
| { |
| const invB:Number = 1 / b; |
| preferredY = Math.max(minY, Math.min(maxY, preferredY)); |
| y = preferredY; |
| |
| // Case3: |
| // bx + d1y >= 0 |
| // y = preferredY |
| x = ( h - d1 * y ) * invB; |
| if (minX <= x && x <= maxX && |
| b * x + d1 * y >= 0) // Satisfy Case3 |
| { |
| // If there is no solution, or the new solution yields smaller value, pick the new solution. |
| if (!s || transformSize(s.x, s.y, matrix).x > transformSize(x, y, matrix).x) |
| s = new Point(x, y); |
| } |
| |
| // Case4: |
| // bx + d1y < 0 |
| // y = preferredY |
| x = ( -h - d1 * y ) * invB; |
| if (minX <= x && x <= maxX && |
| b * x + d1 * y < 0) // Satisfy Case4 |
| { |
| // If there is no solution, or the new solution yields smaller value, pick the new solution. |
| if (!s || transformSize(s.x, s.y, matrix).x > transformSize(x, y, matrix).x) |
| s = new Point(x, y); |
| } |
| } |
| |
| // If there's already a solution that matches preferred dimention, return |
| if (s) |
| return s; |
| |
| // Find a solution that matches the width and minimizes the height: |
| const a:Number = matrix.a; |
| const c:Number = matrix.c; |
| const c1:Number = ( a*c >= 0 ) ? c : -c; |
| return solveEquation(b, d1, h, minX, minY, maxX, maxY, a, c1); |
| } |
| |
| /** |
| * Calculates (x,y) such that the bounding box (0,0,x,y) transformed |
| * with <code>matrix</code> will have bounding box with |
| * width equal to <code>w</code>. |
| * x and y are restricted by <code>minX</code>, <code>maxX</code> and |
| * <code>minY</code>, <code>maxY</code>. |
| * |
| * If possible x will be set to <code>preferredX</code> or |
| * y will be set to <code>preferredY</code>. |
| * |
| * When there are multiple solutions, the function picks the one that |
| * minimizes the bounding box area of transformed (0,0,x,y). |
| * |
| * The functon assumes <code>minX >= 0</code> and <code>minY >= 0</code> |
| * (solution components x and y are non-negative). |
| * |
| * @return Returns Point(x,y) or null if no solution exists. |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| static public function calcUBoundsToFitTBoundsWidth(w:Number, |
| matrix:Matrix, |
| preferredX:Number, |
| preferredY:Number, |
| minX:Number, |
| minY:Number, |
| maxX:Number, |
| maxY:Number):Point |
| { |
| // Untransformed bounds size is (x,y). The corners of the untransformed |
| // bounding box are p1(0,0) p2(x,0) p3(0,y) p4(x,y). |
| // Matrix is | a c tx | |
| // | b d ty | |
| // |
| // After transfomation with the matrix those four points are: |
| // t1 = (0, 0) = matrix.deltaTransformPoint(p1) |
| // t2 = (ax, bx) = matrix.deltaTransformPoint(p2) |
| // t3 = (cy, dy) = matrix.deltaTransformPoint(p3) |
| // t4 = (ax + cy, cx + dy) = matrix.deltaTransformPoint(p4) |
| // |
| // The transformed bounds bounding box dimensions are (w,h): |
| // (1) w = max( t1.x, t2.x, t3.x, t4.x ) - min( t1.x, t2.x, t3.x, t4.x) |
| // (2) h = max( t1.y, t2.y, t3.y, t4.y ) - min( t1.y, t2.y, t3.y, t4.y) |
| // |
| // Looking at all the possible cases for min and max functions above, |
| // we can construct and solve simple linear systems for x and y. |
| // For example in the case of |
| // t1.x <= t2.x <= t3.x <= t4.x |
| // our first equation is |
| // (1) w = t4.x - t1.x <==> w = ax + cy |
| // |
| // To minimize the cases we're looking at we can take advantage of |
| // the limits we have: x >= 0, y >= 0; |
| // Taking into account these limits we deduce that: |
| // a*c >= 0 gives us (1) w = abs( t4.x - t1.x ) = abs( ax + cy ) |
| // a*c < 0 gives us (1) w = abs( t2.x - t3.x ) = abs( ax - cy ) |
| // b*d >= 0 gives us (2) h = abs( t4.y - t1.y ) = abs( bx + dy ) |
| // b*d < 0 gives us (2) h = abs( t2.y - t3.y ) = abs( bx - dy ) |
| // |
| // If we do a substitution such that |
| // c1 = a*c >= 0 ? c : -c |
| // d1 = b*d >= 0 ? d : -d |
| // we get the following linear system: |
| // (1) w = abs( ax + c1y ) |
| // (2) h = abs( bx + d1y ) |
| // |
| // Since we're matching width we only care about (1) |
| |
| var a:Number = matrix.a; |
| var c:Number = matrix.c; |
| |
| // If components are very close to zero, zero them out to handle the special cases |
| if (-1.0e-9 < a && a < +1.0e-9) |
| a = 0; |
| if (-1.0e-9 < c && c < +1.0e-9) |
| c = 0; |
| |
| // Handle special cases first |
| if (a == 0 && c == 0) |
| return null; // No solution |
| |
| if (a == 0) |
| return new Point( preferredX, w / Math.abs(c) ); |
| else if (c == 0) |
| return new Point( w / Math.abs(a), preferredY ); |
| |
| const c1:Number = ( a*c >= 0 ) ? c : -c; |
| // Now we have the following linear sytesm: |
| // (1) w = abs( ax + c1y ) |
| // (2) x = preferredX or y = preferredY |
| |
| var s:Point; |
| var x:Number; |
| var y:Number; |
| |
| if (c1 != 0 && preferredX > 0) |
| { |
| const invC1:Number = 1 / c1; |
| preferredX = Math.max(minX, Math.min(maxX, preferredX)); |
| x = preferredX; |
| |
| // Case1: |
| // a * x + c1 * y >= 0 |
| // x = preferredX |
| y = (w - a * x) * invC1; |
| if (minY <= y && y <= maxY && |
| a * x + c1 * y >= 0 ) // Satisfy Case1 |
| { |
| s = new Point(x, y); |
| } |
| |
| // Case2: |
| // a * x + c1 * y < 0 |
| // x = preferredX |
| y = (-w - a * x) * invC1; |
| if (minY <= y && y <= maxY && |
| a * x + c1 * y < 0 ) // Satisfy Case2 |
| { |
| // If there is no solution, or the new solution yields smaller value, pick the new solution. |
| if (!s || transformSize(s.x, s.y, matrix).y > transformSize(x, y, matrix).y) |
| s = new Point(x, y); |
| } |
| } |
| |
| if (a != 0 && preferredY > 0) |
| { |
| const invA:Number = 1 / a; |
| preferredY = Math.max(minY, Math.min(maxY, preferredY)); |
| y = preferredY; |
| |
| // Case3: |
| // a * x + c1 * y >= 0 |
| // y = preferredY |
| x = (w - c1 * y ) * invA; |
| if (minX <= x && x <= maxX && |
| a * x + c1 * y >= 0) // Satisfy Case3 |
| { |
| // If there is no solution, or the new solution yields smaller value, pick the new solution. |
| if (!s || transformSize(s.x, s.y, matrix).y > transformSize(x, y, matrix).y) |
| s = new Point(x, y); |
| } |
| |
| // Case4: |
| // a * x + c1 * y < 0 |
| // y = preferredY |
| x = (-w - c1 * y ) * invA; |
| if (minX <= x && x <= maxX && |
| a * x + c1 * y < 0) // Satisfy Case4 |
| { |
| // If there is no solution, or the new solution yields smaller value, pick the new solution. |
| if (!s || transformSize(s.x, s.y, matrix).y > transformSize(x, y, matrix).y) |
| s = new Point(x, y); |
| } |
| } |
| |
| // If there's already a solution that matches preferred dimention, return |
| if (s) |
| return s; |
| |
| // Find a solution that matches the width and minimizes the height: |
| const b:Number = matrix.b; |
| const d:Number = matrix.d; |
| const d1:Number = (b*d >= 0) ? d : -d; |
| return solveEquation(a, c1, w, minX, minY, maxX, maxY, b, d1); |
| } |
| |
| /** |
| * Finds a solution (x,y) for the equation abs(a*x + c*y) = w such that |
| * abs(b*x +d*y) is minimized. |
| * If there is infinite number of solutions, x and y are picked to be |
| * as close as possible. |
| * |
| * Doesn't handle cases where <code>a</code> or <code>c</code> are zero. |
| * |
| * @return Returns Point(x,y) |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| static private function solveEquation(a:Number, |
| c:Number, |
| w:Number, |
| minX:Number, |
| minY:Number, |
| maxX:Number, |
| maxY:Number, |
| b:Number, |
| d:Number):Point |
| { |
| if (a == 0 || c == 0) |
| return null; // x and y are not co-dependent |
| |
| // (1) w = abs( ax + cy ) |
| // Find the range of solutsion for y and pick: |
| var x:Number; |
| var y:Number; |
| var s:Point; |
| |
| // Case1: ax + cy >= 0, from (1) above we get: |
| // (1) x = (w - cy) / a |
| // |
| // Lets find the possible range of values for y: |
| // We know that |
| // (3) minX <= x <= maxX |
| // |
| // Substitute x with (w - cy)/a in (3): |
| // (3) minX - w/a <= -cy/a <= maxX - w/a |
| // (3) min( A, B ) <= y <= max( A, B ), where |
| // A = (minX - w/a) * (-a/c) |
| // B = (maxX - w/a) * (-a/c) |
| |
| var A:Number = (w - minX * a) / c; |
| var B:Number = (w - maxX * a) / c; |
| var rangeMinY:Number = Math.max(minY, Math.min(A, B)); |
| var rangeMaxY:Number = Math.min(maxY, Math.max(A, B)); |
| const det:Number = (b * c - a * d); |
| |
| // We have a possible solution for Case1 if the range for y is valid |
| if (rangeMinY <= rangeMaxY) |
| { |
| // Now that we have a valid range for y, we need to pick a value within |
| // that range. |
| // |
| // We calculate the value based on a custom condition. |
| // |
| // The custom condition that we use could be anything that defines |
| // another equation for x and y. Some examples are: |
| // "make x and y as close as possible": y = w / ( a + c ); |
| // "minimize abs(bx + dy)": y = b * w / det |
| // "preserve aspect ratio": y = w / ( a * preferredX / preferredY + c ); |
| if (Math.abs(det) < 1.0e-9) |
| { |
| // There is infinite number of solutions, lets pick x == y |
| y = w / ( a + c ); |
| } |
| else |
| { |
| // Minimize abs(bx + dy) - we need to solve: |
| // abs( b * ( w - c * y ) / a + d * y ) = 0 |
| // which gives us: |
| y = b * w / det; |
| } |
| |
| // Now that we have the y value calculated from the custom condition, |
| // we clamp with the range. This gives us a solution with |
| // values as close as possible to satisfy our custom condition when |
| // the condition is a linear function of x and y (in our case it is). |
| y = Math.max(rangeMinY, Math.min(y, rangeMaxY)); |
| |
| x = (w - c * y) / a; |
| return new Point(x, y); |
| } |
| |
| // Case2: ax + cy < 0, from (1) above we get: |
| // (1) x = (-w - cy) / a |
| // |
| // Lets find the possible range of values for y: |
| // We know that |
| // (3) minX <= x <= maxX |
| // |
| // Substitute x with (-w - cy)/a in (3): |
| // (3) minX + w/a <= -cy/a <= maxX + w/a |
| // (3) min( A, B ) <= y <= max( A, B ), where |
| // A = (minX + w/a) * (-a/c) |
| // B = (maxX + w/a) * (-a/c) |
| |
| A = -(minX * a + w) / c; |
| B = -(maxX * a + w) / c; |
| rangeMinY = Math.max(minY, Math.min(A, B)); |
| rangeMaxY = Math.min(maxY, Math.max(A, B)); |
| |
| // We have a possible solution for Case2 if the range for y is valid |
| if (rangeMinY <= rangeMaxY) |
| { |
| // Now that we have a valid range for y, we need to pick a value within |
| // that range. |
| // |
| // We calculate the value based on a custom condition. |
| // |
| // The custom condition that we use could be anything that defines |
| // another equation for x and y. Some examples are: |
| // "make x and y as close as possible": y = -w / ( a + c ); |
| // "minimize abs(bx + dy)": y = -b * w / det |
| // "preserve aspect ratio": y = w / ( a * preferredX / preferredY + c ); |
| if (Math.abs(det) < 1.0e-9) |
| { |
| // There is infinite number of solutions, lets pick x == y |
| y = -w / ( a + c ); |
| } |
| else |
| { |
| // Minimize abs(bx + dy) - we need to solve: |
| // abs( b * ( -w - c * y ) / a + d * y ) = 0 |
| // which gives us: |
| y = -b * w / det; |
| } |
| |
| // Now that we have the y value calculated from the custom condition, |
| // we clamp with the range. This gives us a solution with |
| // values as close as possible to satisfy our custom condition when |
| // the condition is a linear function of x and y (in our case it is). |
| y = Math.max(rangeMinY, Math.min(y, rangeMaxY)); |
| x = (-w - c * y) / a; |
| return new Point(x, y); |
| |
| } |
| return null; // No solution |
| } |
| |
| /** |
| * Calculates (x,y) such that the bounding box (0,0,x,y) transformed |
| * with <code>matrix</code> will have bounding box (0,0,w,h). |
| * x and y are restricted by <code>minX</code>, <code>maxX</code> and |
| * <code>minY</code>, <code>maxY</code>. |
| * |
| * When there is infinite number of solutions, the function will |
| * calculate x and y to be as close as possible. |
| * |
| * The functon assumes <code>minX >= 0</code> and <code>minY >= 0</code> |
| * (solution components x and y are non-negative). |
| * |
| * @return Point(x,y) or null if no solution exists. |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| static public function calcUBoundsToFitTBounds(w:Number, |
| h:Number, |
| matrix:Matrix, |
| minX:Number, |
| minY:Number, |
| maxX:Number, |
| maxY:Number):Point |
| { |
| // Untransformed bounds size is (x,y). The corners of the untransformed |
| // bounding box are p1(0,0) p2(x,0) p3(0,y) p4(x,y). |
| // Matrix is | a c tx | |
| // | b d ty | |
| // |
| // After transfomation with the matrix those four points are: |
| // t1 = (0, 0) = matrix.deltaTransformPoint(p1) |
| // t2 = (ax, bx) = matrix.deltaTransformPoint(p2) |
| // t3 = (cy, dy) = matrix.deltaTransformPoint(p3) |
| // t4 = (ax + cy, cx + dy) = matrix.deltaTransformPoint(p4) |
| // |
| // The transformed bounds bounding box dimensions are (w,h): |
| // (1) w = max( t1.x, t2.x, t3.x, t4.x ) - min( t1.x, t2.x, t3.x, t4.x) |
| // (2) h = max( t1.y, t2.y, t3.y, t4.y ) - min( t1.y, t2.y, t3.y, t4.y) |
| // |
| // Looking at all the possible cases for min and max functions above, |
| // we can construct and solve simple linear systems for x and y. |
| // For example in the case of |
| // t1.x <= t2.x <= t3.x <= t4.x |
| // our first equation is |
| // (1) w = t4.x - t1.x <==> w = ax + cy |
| // |
| // To minimize the cases we're looking at we can take advantage of |
| // the limits we have: x >= 0, y >= 0; |
| // Taking into account these limits we deduce that: |
| // a*c >= 0 gives us (1) w = abs( t4.x - t1.x ) = abs( ax + cy ) |
| // a*c < 0 gives us (1) w = abs( t2.x - t3.x ) = abs( ax - cy ) |
| // b*d >= 0 gives us (2) h = abs( t4.y - t1.y ) = abs( bx + dy ) |
| // b*d < 0 gives us (2) h = abs( t2.y - t3.y ) = abs( bx - dy ) |
| // |
| // If we do a substitution such that |
| // c1 = a*c >= 0 ? c : -c |
| // d1 = b*d >= 0 ? d : -d |
| // we get the following linear system: |
| // (1) w = abs( ax + c1y ) |
| // (2) h = abs( bx + d1y ) |
| // |
| |
| var a:Number = matrix.a; |
| var b:Number = matrix.b; |
| var c:Number = matrix.c; |
| var d:Number = matrix.d; |
| |
| // If components are very close to zero, zero them out to handle the special cases |
| if (-1.0e-9 < a && a < +1.0e-9) |
| a = 0; |
| if (-1.0e-9 < b && b < +1.0e-9) |
| b = 0; |
| if (-1.0e-9 < c && c < +1.0e-9) |
| c = 0; |
| if (-1.0e-9 < d && d < +1.0e-9) |
| d = 0; |
| |
| // Handle special cases. |
| if (b == 0 && c == 0) |
| { |
| // No solution in the following cases since the matrix collapses |
| // all points into a line. |
| if (a == 0 || d == 0) |
| return null; |
| |
| // (1) w = abs( ax + cy ) <=> w = abs( ax ) <=> w = abs(a)x |
| // (2) h = abs( bx + dy ) <=> h = abs( dy ) <=> h = abs(d)y |
| return new Point(w / Math.abs(a), h / Math.abs(d)); |
| } |
| |
| if (a == 0 && d == 0) |
| { |
| // No solution in the following cases since the matrix collapses |
| // all points into a line. |
| if (b == 0 || c == 0) |
| return null; |
| |
| // (1) w = abs( ax + cy ) <=> w = abs( cy ) <=> w = abs(c)y |
| // (2) h = abs( bx + dy ) <=> h = abs( bx ) <=> h = abs(b)x |
| return new Point(h / Math.abs(b), w / Math.abs(c)); |
| } |
| |
| // Handle general cases. |
| const c1:Number = ( a*c >= 0 ) ? c : -c; |
| const d1:Number = ( b*d >= 0 ) ? d : -d; |
| // we get the following linear system: |
| // (1) w = abs( ax + c1y ) |
| // (2) h = abs( bx + d1y ) |
| |
| // Calculate the determinant of the system |
| const det:Number = a * d1 - b * c1; |
| if (Math.abs(det) < 1.0e-9) |
| { |
| // No solution in these cases since the matrix |
| // collapses all points into a line. |
| if (c1 == 0 || a == 0 || a == -c1) |
| return null; |
| |
| if (Math.abs(a * h - b * w) > 1.0e-9) |
| return null; // No solution in this case |
| |
| // Determinant is zero, the equations (1) & (2) are equivalent and |
| // we have only one equation: |
| // (1) w = abs( ax + c1y ) |
| // |
| // Solve it finding x and y as close as possible: |
| return solveEquation(a, c1, w, minX, minX, maxX, maxY, b, d1); |
| } |
| |
| // Pre-multiply w & h by the inverse dteterminant |
| const invDet:Number = 1 / det; |
| w *= invDet; |
| h *= invDet; |
| |
| // Case 1: |
| // a * x + c1 * y >= 0 |
| // b * x + d1 * y >= 0 |
| var s:Point; |
| s = solveSystem(a, c1, b, d1, w, h); |
| if (s && |
| minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && |
| a * s.x + c1 * s.x >= 0 && |
| b * s.x + d1 * s.y >= 0) |
| return s; |
| |
| // Case 2: |
| // a * x + c1 * y >= 0 |
| // b * x + d1 * y < 0 |
| s = solveSystem( a, c1, b, d1, w, -h); |
| if (s && |
| minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && |
| a * s.x + c1 * s.x >= 0 && |
| b * s.x + d1 * s.y < 0) |
| return s; |
| |
| // Case 3: |
| // a * x + c1 * y < 0 |
| // b * x + d1 * y >= 0 |
| s = solveSystem( a, c1, b, d1, -w, h); |
| if (s && |
| minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && |
| a * s.x + c1 * s.x < 0 && |
| b * s.x + d1 * s.y >= 0) |
| return s; |
| |
| // Case 4: |
| // a * x + c1 * y < 0 |
| // b * x + d1 * y < 0 |
| s = solveSystem( a, c1, b, d1, -w, -h); |
| if (s && |
| minX <= s.x && s.x <= maxX && minY <= s.y && s.y <= maxY && |
| a * s.x + c1 * s.x < 0 && |
| b * s.x + d1 * s.y < 0) |
| return s; |
| |
| return null; // No solution. |
| } |
| |
| /** |
| * Determine if two Matrix instances are equal. |
| * |
| * @return true if the matrices are equal. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function isEqual(m1:Matrix, m2:Matrix):Boolean |
| { |
| return ((m1 && m2 && |
| m1.a == m2.a && |
| m1.b == m2.b && |
| m1.c == m2.c && |
| m1.d == m2.d && |
| m1.tx == m2.tx && |
| m1.ty == m2.ty) || |
| (!m1 && !m2)); |
| } |
| |
| /** |
| * Determine if two Matrix3D instances are equal. |
| * |
| * @return true if the matrices are equal. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function isEqual3D(m1:Matrix3D, m2:Matrix3D):Boolean |
| { |
| if (m1 && m2 && m1.rawData.length == m2.rawData.length) |
| { |
| var r1:Vector.<Number> = m1.rawData; |
| var r2:Vector.<Number> = m2.rawData; |
| |
| return (r1[0] == r2[0] && |
| r1[1] == r2[1] && |
| r1[2] == r2[2] && |
| r1[3] == r2[3] && |
| r1[4] == r2[4] && |
| r1[5] == r2[5] && |
| r1[6] == r2[6] && |
| r1[7] == r2[7] && |
| r1[8] == r2[8] && |
| r1[9] == r2[9] && |
| r1[10] == r2[10] && |
| r1[11] == r2[11] && |
| r1[12] == r2[12] && |
| r1[13] == r2[13] && |
| r1[14] == r2[14] && |
| r1[15] == r2[15]); |
| } |
| |
| return (!m1 && !m2); |
| } |
| |
| /** |
| * Calculates (x,y) such as to satisfy the linear system: |
| * | a * x + c * y = m |
| * | b * x + d * y = n |
| * |
| * @param mOverDet <code>mOverDet must be equal to m / (a*d - b*c)</code> |
| * @param nOverDet <code>mOverDet must be equal to n / (a*d - b*c)</code> |
| * |
| * @return returns Point(x,y) |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| static private function solveSystem(a:Number, |
| c:Number, |
| b:Number, |
| d:Number, |
| mOverDet:Number, |
| nOverDet:Number):Point |
| { |
| return new Point(d * mOverDet - c * nOverDet, |
| a * nOverDet - b * mOverDet); |
| } |
| |
| /** |
| * Workaround for player's concatenatedMatrix being wrong in some situations, such |
| * as when there is a filter or a scrollRect somewhere in the object's container |
| * hierarchy. Walk the parent tree manually, calculating the matrix manually. |
| * |
| * @param displayObject Calculate the concatenatedMatrix for this displayObject |
| * |
| * @param topParent <p>When specified, the matrix is computed up to the topParent, |
| * excluding topParent's concatenated matrix. This is useful when computing a transform |
| * in order to move an object to a different parent but the object's transform needs |
| * to be adjusted in order to keep its original position on screen.</p> |
| * |
| * @return The concatenatedMatrix for the displayObject |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public static function getConcatenatedMatrix(displayObject:DisplayObject, topParent:DisplayObject):Matrix |
| { |
| return getConcatenatedMatrixHelper(displayObject, false, topParent); |
| } |
| |
| /** |
| * Workaround for player's concatenatedMatrix being wrong in some situations, such |
| * as when there is a filter or a scrollRect somewhere in the object's container |
| * hierarchy. Walk the parent tree manually, calculating the matrix manually. |
| * |
| * This function differs from getConcatenatedMatrix in that it combines the |
| * computedMatrix of each ancestor. The computedMatrix includes transform offsets. |
| * |
| * @param displayObject Calculate the concatenatedMatrix for this displayObject |
| * |
| * @param topParent <p>When specified, the matrix is computed up to the topParent, |
| * excluding topParent's concatenated matrix. This is useful when computing a transform |
| * in order to move an object to a different parent but the object's transform needs |
| * to be adjusted in order to keep its original position on screen.</p> |
| * |
| * @return The concatenatedMatrix for the displayObject |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public static function getConcatenatedComputedMatrix(displayObject:DisplayObject, topParent:DisplayObject):Matrix |
| { |
| return getConcatenatedMatrixHelper(displayObject, true, topParent); |
| } |
| |
| /** |
| * @private |
| */ |
| private static function getConcatenatedMatrixHelper(displayObject:DisplayObject, useComputedMatrix:Boolean, topParent:DisplayObject):Matrix |
| { |
| var m:Matrix = new Matrix(); |
| |
| // This check should be made once per top-level ApplicationDomain |
| if (usesMarshalling == null) |
| { |
| // Check if marshalling support has been turned on |
| usesMarshalling = ApplicationDomain.currentDomain.hasDefinition("mx.managers.systemClasses.MarshallingSupport"); |
| |
| // If we aren't using marshalling, then we only have one ApplicationDomain and thus one class |
| // definition for UIComponent |
| if (!usesMarshalling && ApplicationDomain.currentDomain.hasDefinition("mx.core.UIComponent")) |
| uiComponentClass = Class(ApplicationDomain.currentDomain.getDefinition("mx.core.UIComponent")); |
| // same thing for UIMovieClip |
| if (!usesMarshalling && ApplicationDomain.currentDomain.hasDefinition("mx.flash.UIMovieClip")) |
| uiMovieClipClass = Class(ApplicationDomain.currentDomain.getDefinition("mx.flash.UIMovieClip")); |
| } |
| |
| // Note, root will be "null" if the displayObject is off the display list. In particular, |
| // during start-up, before applicationComplete is dispatched, root will be null. |
| // Note that getConcatenatedMatrixHelper() with topParent == sandboxRoot will still work |
| // correctly in those cases as we use ".$parent" to walk up the parent chain and during start-up |
| // $parent will be null for the application before applicationComplete has been dispatched. |
| |
| if (fakeDollarParent == null) |
| fakeDollarParent = new QName(mx_internal, "$parent"); |
| |
| if (useComputedMatrix && computedMatrixProperty == null) |
| computedMatrixProperty = new QName(mx_internal, "computedMatrix"); |
| |
| if ($transformProperty == null) |
| $transformProperty = new QName(mx_internal, "$transform"); |
| |
| |
| var displayObjectMatrix:Matrix = displayObject ? displayObject.transform.matrix : null ; |
| var notTopAndHasMatrix:Boolean = displayObjectMatrix!=null && displayObject != topParent; |
| while (notTopAndHasMatrix) |
| { |
| var scrollRect:Rectangle = displayObject.scrollRect; |
| if (scrollRect != null) |
| m.translate(-scrollRect.x, -scrollRect.y); |
| |
| // If we are using marshalling, we can have multiple class definitions of UIComponent |
| if (usesMarshalling && "moduleFactory" in displayObject) |
| { |
| var moduleFactory:Object = displayObject["moduleFactory"]; |
| // If the module factory has changed, then we are in a different ApplicationDomain |
| if (moduleFactory && moduleFactory !== lastModuleFactory && "info" in moduleFactory) |
| { |
| var appDomain:ApplicationDomain; |
| |
| appDomain = moduleFactory["info"]()["currentDomain"]; |
| // Get the class definition for UIComponent in the current ApplicationDomain |
| if (appDomain && appDomain.hasDefinition("mx.core.UIComponent")) |
| uiComponentClass = Class(appDomain.getDefinition("mx.core.UIComponent")); |
| // same thing for UIMovieClip |
| if (appDomain && appDomain.hasDefinition("mx.flash.UIMovieClip")) |
| uiMovieClipClass = Class(appDomain.getDefinition("mx.flash.UIMovieClip")); |
| |
| lastModuleFactory = moduleFactory; |
| } |
| } |
| |
| var isUIComponent:Boolean = uiComponentClass && displayObject is uiComponentClass; |
| var isUIMovieClip:Boolean = uiMovieClipClass && displayObject is uiMovieClipClass; |
| |
| if (useComputedMatrix && isUIComponent) |
| m.concat(displayObject[computedMatrixProperty]); |
| else if (isUIMovieClip) |
| m.concat(displayObject[$transformProperty].matrix); |
| else |
| m.concat(displayObjectMatrix); |
| |
| // Try to access $parent, which is the true display list parent |
| if (isUIComponent) |
| displayObject = displayObject[fakeDollarParent] as DisplayObject; |
| else |
| displayObject = displayObject.parent as DisplayObject; |
| |
| displayObjectMatrix = displayObject ? displayObject.transform.matrix : null; |
| notTopAndHasMatrix = displayObjectMatrix!=null && displayObject != topParent; |
| } |
| return m; |
| } |
| |
| } |
| |
| } |