http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/46eaea76/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/CompoundTransform.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/CompoundTransform.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/CompoundTransform.as new file mode 100644 index 0000000..7b1916b --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/CompoundTransform.as @@ -0,0 +1,777 @@ +/** + * Licensed 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 org.apache.flex.core.graphics.utils +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + import __AS3__.vec.Vector; + + /** + * A CompoundTransform represents a 2D or 3D matrix transform. A compound transform represents a matrix that can be queried or set either as a 2D matrix, + * a 3D matrix, or as individual convenience transform properties such as x, y, scaleX, rotationZ, etc. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public class CompoundTransform + { + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + /** + * Constructor. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function CompoundTransform() + { + } + + + + /** + * @private + * storage for transform properties. These values are concatenated together with the layout properties to + * form the actual computed matrix used to render the object. + */ + private var _rotationX:Number = 0; + private var _rotationY:Number = 0; + private var _rotationZ:Number = 0; + private var _scaleX:Number = 1; + private var _scaleY:Number = 1; + private var _scaleZ:Number = 1; + private var _x:Number = 0; + private var _y:Number = 0; + private var _z:Number = 0; + + private var _transformX:Number = 0; + private var _transformY:Number = 0; + private var _transformZ:Number = 0; + + + /** + * @private + * slots for the 2D and 3D matrix transforms. Note that + * these are only allocated and computed on demand -- many component instances will never use a 3D + * matrix, for example. + */ + private var _matrix:Matrix; + private var _matrix3D:Matrix3D; + + + /** + * @private + * bit field flags for indicating which transforms are valid -- the layout properties, the matrices, + * and the 3D matrices. Since developers can set any of the three programmatically, the last one set + * will always be valid, and the others will be invalid until validated on demand. + */ + private static const MATRIX_VALID:uint = 0x20; + private static const MATRIX3D_VALID:uint = 0x40; + private static const PROPERTIES_VALID:uint = 0x80; + + + /** + * @private + * flags for tracking whether the transform is 3D. A transform is 3D if any of the 3D properties -- rotationX/Y, scaleZ, or z -- are set. + */ + private static const IS_3D:uint = 0x200; + private static const M3D_FLAGS_VALID:uint = 0x400; + + /** + * @private + * constants to indicate which form of a transform -- the properties, matrix, or matrix3D -- is + * 'the source of truth.' + */ + public static const SOURCE_PROPERTIES:uint = 1; + /** + * @private + * constants to indicate which form of a transform -- the properties, matrix, or matrix3D -- is + * 'the source of truth.' + */ + public static const SOURCE_MATRIX:uint = 2; + /** + * @private + * constants to indicate which form of a transform -- the properties, matrix, or matrix3D -- is + * 'the source of truth.' + */ + public static const SOURCE_MATRIX3D:uint = 3; + + /** + * @private + * indicates the 'source of truth' for the transform. + */ + public var sourceOfTruth:uint = SOURCE_PROPERTIES; + + /** + * @private + * general storage for all of ur flags. + */ + private var _flags:uint = PROPERTIES_VALID; + + /** + * @private + * flags that get passed to the invalidate method indicating why the invalidation is happening. + */ + private static const INVALIDATE_FROM_NONE:uint = 0; + private static const INVALIDATE_FROM_PROPERTY:uint = 4; + private static const INVALIDATE_FROM_MATRIX:uint = 5; + private static const INVALIDATE_FROM_MATRIX3D:uint = 6; + + /** + * @private + * static data used by utility methods below + */ + private static var decomposition:Vector.<Number> = new Vector.<Number>(); + decomposition.push(0); + decomposition.push(0); + decomposition.push(0); + decomposition.push(0); + decomposition.push(0); + + private static const RADIANS_PER_DEGREES:Number = Math.PI / 180; + + //---------------------------------------------------------------------------- + + /** + * The x value of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set x(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _x) + return; + translateBy(value-_x,0,0); + invalidate(INVALIDATE_FROM_PROPERTY, false /*affects3D*/); + } + /** + * @private + */ + public function get x():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _x; + } + + /** + * The y value of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set y(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _y) + return; + translateBy(0,value-_y,0); + invalidate(INVALIDATE_FROM_PROPERTY, false /*affects3D*/); + } + + /** + * @private + */ + public function get y():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _y; + } + + /** + * The z value of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set z(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _z) + return; + translateBy(0,0,value-_z); + invalidate(INVALIDATE_FROM_PROPERTY, true /*affects3D*/); + } + + /** + * @private + */ + public function get z():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _z; + } + + //------------------------------------------------------------------------------ + + + /** + * The rotationX, in degrees, of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set rotationX(value:Number):void + { + // clamp the rotation value between -180 and 180. This is what + // the Flash player does, so let's mimic it here too. + value = MatrixUtil.clampRotation(value); + + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _rotationX) + return; + _rotationX = value; + invalidate(INVALIDATE_FROM_PROPERTY, true /*affects3D*/); + } + + /** + * @private + */ + public function get rotationX():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _rotationX; + } + + /** + * The rotationY, in degrees, of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set rotationY(value:Number):void + { + // clamp the rotation value between -180 and 180. This is what + // the Flash player does, so let's mimic it here too. + value = MatrixUtil.clampRotation(value); + + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _rotationY) + return; + _rotationY = value; + invalidate(INVALIDATE_FROM_PROPERTY, true /*affects3D*/); + } + + /** + * @private + */ + public function get rotationY():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _rotationY; + } + + /** + * The rotationZ, in degrees, of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set rotationZ(value:Number):void + { + // clamp the rotation value between -180 and 180. This is what + // the Flash player does, so let's mimic it here too. + value = MatrixUtil.clampRotation(value); + + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _rotationZ) + return; + _rotationZ = value; + invalidate(INVALIDATE_FROM_PROPERTY, false /*affects3D*/); + } + + /** + * @private + */ + public function get rotationZ():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _rotationZ; + } + + //------------------------------------------------------------------------------ + + + /** + * The scaleX of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set scaleX(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _scaleX) + return; + _scaleX = value; + invalidate(INVALIDATE_FROM_PROPERTY, false /*affects3D*/); + } + + /** + * @private + */ + public function get scaleX():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _scaleX; + } + + /** + * The scaleY of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set scaleY(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _scaleY) + return; + _scaleY = value; + invalidate(INVALIDATE_FROM_PROPERTY, false /*affects3D*/); + } + + /** + * @private + */ + public function get scaleY():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _scaleY; + } + + + /** + * The scaleZ of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set scaleZ(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _scaleZ) + return; + _scaleZ = value; + invalidate(INVALIDATE_FROM_PROPERTY, true /*affects3D*/); + } + + /** + * @private + */ + public function get scaleZ():Number + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + return _scaleZ; + } + + + /** + * @private + * returns true if the transform has 3D values. + */ + public function get is3D():Boolean + { + if ((_flags & M3D_FLAGS_VALID) == 0) + update3DFlags(); + return ((_flags & IS_3D) != 0); + } + + //------------------------------------------------------------------------------ + /** + * The x value of the transform center. The transform center is kept fixed as rotation and scale are applied. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set transformX(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _transformX) + return; + _transformX = value; + invalidate(INVALIDATE_FROM_PROPERTY, true /*affects3D*/); + } + + /** + * @private + */ + public function get transformX():Number + { + return _transformX; + } + + //------------------------------------------------------------------------------ + /** + * The y value of the tansform center. The transform center is kept fixed as rotation and scale are applied. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set transformY(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _transformY) + return; + _transformY = value; + invalidate(INVALIDATE_FROM_PROPERTY, true /*affects3D*/); + } + + /** + * @private + */ + public function get transformY():Number + { + return _transformY; + } + //------------------------------------------------------------------------------ + /** + * The z value of the tansform center. The transform center is kept fixed as rotation and scale are applied. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function set transformZ(value:Number):void + { + if ((_flags & PROPERTIES_VALID) == false) validatePropertiesFromMatrix(); + if (value == _transformZ) + return; + _transformZ = value; + invalidate(INVALIDATE_FROM_PROPERTY, true /*affects3D*/); + } + + /** + * @private + */ + public function get transformZ():Number + { + return _transformZ; + } + + //------------------------------------------------------------------------------ + + /** + * @private + * invalidates our various cached values. Any change to the CompoundTransform object that affects + * the various transforms should call this function. + * @param reason - the code indicating what changes to cause the invalidation. + * @param affects3D - a flag indicating whether the change affects the 2D/3D nature of the various transforms. + * @param dispatchChangeEvent - if true, the CompoundTransform will dispatch a change indicating that its underlying transforms + * have been modified. + */ + private function invalidate(reason:uint, affects3D:Boolean):void + { + //race("invalidating: " + reason); + switch(reason) + { + case INVALIDATE_FROM_PROPERTY: + sourceOfTruth = SOURCE_PROPERTIES; + _flags |= PROPERTIES_VALID; + _flags &= ~MATRIX_VALID; + _flags &= ~MATRIX3D_VALID; + break; + case INVALIDATE_FROM_MATRIX: + sourceOfTruth = SOURCE_MATRIX; + _flags |= MATRIX_VALID; + _flags &= ~PROPERTIES_VALID; + _flags &= ~MATRIX3D_VALID; + break; + case INVALIDATE_FROM_MATRIX3D: + sourceOfTruth = SOURCE_MATRIX3D; + _flags |= MATRIX3D_VALID; + _flags &= ~PROPERTIES_VALID; + _flags &= ~MATRIX_VALID; + break; + } + if (affects3D) + _flags &= ~M3D_FLAGS_VALID; + + } + + private static const EPSILON:Number = .001; + /** + * @private + * updates the flags that indicate whether the layout, offset, and/or computed transforms are 3D in nature. + * Since the user can set either the individual transform properties or the matrices directly, we compute these + * flags based on what the current 'source of truth' is for each of these values. + */ + private function update3DFlags():void + { + if ((_flags & M3D_FLAGS_VALID) == 0) + { + var matrixIs3D:Boolean = false; + + switch(sourceOfTruth) + { + case SOURCE_PROPERTIES: + matrixIs3D = ( // note that rotationZ is the same as rotation, and not a 3D affecting + (Math.abs(_scaleZ-1) > EPSILON) || // property. + ((Math.abs(_rotationX)+EPSILON)%360) > 2*EPSILON || + ((Math.abs(_rotationY)+EPSILON)%360) > 2*EPSILON || + Math.abs(_z) > EPSILON + ); + break; + case SOURCE_MATRIX: + matrixIs3D = false; + break; + case SOURCE_MATRIX3D: + var rawData:Vector.<Number> = _matrix3D.rawData; + matrixIs3D = (rawData[2] != 0 || // rotation y + rawData[6] != 0 || // rotation x + rawData[8] !=0 || // rotation y + rawData[10] != 1 || // scalez / rotation x / rotation y + rawData[14] != 0); // translation z + break; + } + + if (matrixIs3D) + _flags |= IS_3D; + else + _flags &= ~IS_3D; + + _flags |= M3D_FLAGS_VALID; + } + } + + + /** + * Applies the delta to the transform's translation component. Unlike setting the x, y, or z properties directly, + * this method can be safely called without changing the transform's concept of 'the source of truth'. + * + * @param x The x value of the transform. + * + * @param y The y value of the transform. + * + * @param z The z value of the transform. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function translateBy(x:Number,y:Number,z:Number = 0):void + { + if (_flags & MATRIX_VALID) + { + _matrix.tx += x; + _matrix.ty += y; + } + if (_flags & PROPERTIES_VALID) + { + _x += x; + _y += y; + _z += z; + } + if (_flags & MATRIX3D_VALID) + { + var data:Vector.<Number> = _matrix3D.rawData; + data[12] += x; + data[13] += y; + data[14] += z; + _matrix3D.rawData = data; + } + invalidate(INVALIDATE_FROM_NONE, z != 0 /*affects3D*/); + } + + + /** + * The 2D matrix either set directly by the user, or composed by combining the transform center, scale, rotation + * and translation, in that order. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function get matrix():Matrix + { + + if (_flags & MATRIX_VALID) + return _matrix; + + if ((_flags & PROPERTIES_VALID) == false) + validatePropertiesFromMatrix(); + + var m:Matrix = _matrix; + if (m == null) + m = _matrix = new Matrix(); + else + m.identity(); + + AdvancedLayoutFeatures.build2DMatrix(m,_transformX,_transformY, + _scaleX,_scaleY, + _rotationZ, + _x,_y); + _flags |= MATRIX_VALID; + return m; + } + + /** + * @private + */ + public function set matrix(v:Matrix):void + { + if (_matrix== null) + { + _matrix = v.clone(); + } + else + { + _matrix.identity(); + _matrix.concat(v); + } + + // affects3D is true since setting matrix changes the transform to 2D + invalidate(INVALIDATE_FROM_MATRIX, true /*affects3D*/); + } + + /** + * @private + * decomposes the offset transform matrices down into the convenience offset properties. Note that this is not + * a bi-directional transformation -- it is possible to create a matrix that can't be fully represented in the + * convenience properties. This function will pull from the matrix or matrix3D values, depending on which was most + * recently set + */ + private function validatePropertiesFromMatrix():void + { + if (sourceOfTruth == SOURCE_MATRIX3D) + { + var result:Vector.<Vector3D> = _matrix3D.decompose(); + _rotationX = result[1].x / RADIANS_PER_DEGREES; + _rotationY = result[1].y / RADIANS_PER_DEGREES; + _rotationZ = result[1].z / RADIANS_PER_DEGREES; + _scaleX = result[2].x; + _scaleY = result[2].y; + _scaleZ = result[2].z; + + if (_transformX != 0 || _transformY != 0 || _transformZ != 0) + { + var postTransformTCenter:Vector3D = _matrix3D.transformVector(new Vector3D(_transformX,_transformY,_transformZ)); + _x = postTransformTCenter.x - _transformX; + _y = postTransformTCenter.y - _transformY; + _z = postTransformTCenter.z - _transformZ; + } + else + { + _x = result[0].x; + _y = result[0].y; + _z = result[0].z; + } + } + else if (sourceOfTruth == SOURCE_MATRIX) + { + MatrixUtil.decomposeMatrix(decomposition,_matrix,_transformX,_transformY); + _x = decomposition[0]; + _y = decomposition[1]; + _z = 0; + _rotationX = 0; + _rotationY = 0; + _rotationZ = decomposition[2]; + _scaleX = decomposition[3]; + _scaleY = decomposition[4]; + _scaleZ = 1; + } + _flags |= PROPERTIES_VALID; + + } + + + + /** + * The 3D matrix either set directly by the user, or composed by combining the transform center, scale, rotation + * and translation, in that order. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion Flex 3 + */ + public function get matrix3D():Matrix3D + { + if (_flags & MATRIX3D_VALID) + return _matrix3D; + + if ((_flags & PROPERTIES_VALID) == false) + validatePropertiesFromMatrix(); + + var m:Matrix3D = _matrix3D; + if (m == null) + m = _matrix3D = new Matrix3D(); + else + m.identity(); + + AdvancedLayoutFeatures.build3DMatrix(m,transformX,transformY,transformZ, + _scaleX,_scaleY,_scaleZ, + _rotationX,_rotationY,_rotationZ, + _x,_y,_z); + _flags |= MATRIX3D_VALID; + return m; + + } + + /** + * @private + */ + public function set matrix3D(v:Matrix3D):void + { + if (_matrix3D == null) + { + _matrix3D = v.clone(); + } + else + { + _matrix3D.identity(); + if (v) + _matrix3D.append(v); + } + invalidate(INVALIDATE_FROM_MATRIX3D, true /*affects3D*/); + } + + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/46eaea76/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/IAssetLayoutFeatures.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/IAssetLayoutFeatures.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/IAssetLayoutFeatures.as new file mode 100644 index 0000000..1e36b60 --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/IAssetLayoutFeatures.as @@ -0,0 +1,371 @@ +/** + * Licensed 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 org.apache.flex.core.graphics.utils +{ + import flash.geom.Matrix; + import flash.geom.Matrix3D; + + + /** + * The IAssetLayoutFeatures interface defines the minimum properties and methods + * required for an Object to support advanced transforms in embedded assets. + * + * @see mx.core.AdvancedLayoutFeatures + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + public interface IAssetLayoutFeatures + { + + /** + * Layout transform convenience property. Represents the x value of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutX(value:Number):void; + + /** + * @private + */ + function get layoutX():Number; + + /** + * Layout transform convenience property. Represents the y value of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutY(value:Number):void; + + /** + * @private + */ + function get layoutY():Number; + + /** + * Layout transform convenience property. Represents the z value of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutZ(value:Number):void; + + /** + * @private + */ + function get layoutZ():Number; + + /** + * Used by the mirroring transform. See the mirror property. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get layoutWidth():Number; + + /** + * @private + */ + function set layoutWidth(value:Number):void; + + //------------------------------------------------------------------------------ + + /** + * The x value of the point around which any rotation and scale is performed in both the layout and computed matrix. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set transformX(value:Number):void; + /** + * @private + */ + function get transformX():Number; + + /** + * The y value of the point around which any rotation and scale is performed in both the layout and computed matrix. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set transformY(value:Number):void; + + /** + * @private + */ + function get transformY():Number; + + /** + * The z value of the point around which any rotation and scale is performed in both the layout and computed matrix. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set transformZ(value:Number):void; + + /** + * @private + */ + function get transformZ():Number; + + //------------------------------------------------------------------------------ + + /** + * Layout transform convenience property. Represents the rotation around the X axis of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutRotationX(value:Number):void; + + /** + * @private + */ + function get layoutRotationX():Number; + + /** + * Layout transform convenience property. Represents the rotation around the Y axis of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutRotationY(value:Number):void; + + /** + * @private + */ + function get layoutRotationY():Number; + + /** + * Layout transform convenience property. Represents the rotation around the Z axis of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutRotationZ(value:Number):void; + + /** + * @private + */ + function get layoutRotationZ():Number; + + //------------------------------------------------------------------------------ + + /** + * Layout transform convenience property. Represents the scale along the X axis of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutScaleX(value:Number):void; + + /** + * @private + */ + function get layoutScaleX():Number; + + /** + * Layout transform convenience property. Represents the scale along the Y axis of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutScaleY(value:Number):void; + + /** + * @private + */ + function get layoutScaleY():Number; + + /** + * Layout transform convenience property. Represents the scale along the Z axis of the layout matrix used in layout and in + * the computed transform. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutScaleZ(value:Number):void; + + /** + * @private + */ + function get layoutScaleZ():Number; + + /** + * The 2D matrix used during layout calculations to determine the layout and size of the component and its parent and siblings. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutMatrix(value:Matrix):void; + + /** + * @private + */ + function get layoutMatrix():Matrix; + + /** + * The 3D matrix used during layout calculations to determine the layout and size of the component and its parent and siblings. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function set layoutMatrix3D(value:Matrix3D):void; + + /** + * @private + */ + function get layoutMatrix3D():Matrix3D; + + /** + * True if the computed transform has 3D values. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get is3D():Boolean; + + /** + * True if the layout transform has 3D values. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get layoutIs3D():Boolean; + + /** + * If true the X axis is scaled by -1 and the x coordinate of the origin + * is translated by the component's width. + * + * The net effect of this "mirror" transform is to flip the direction + * that the X axis increases in without changing the layout element's + * location relative to the parent's origin. + * + * @default false + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get mirror():Boolean; + + /** + * @private + */ + function set mirror(value:Boolean):void; + + + /** + * The stretchY is the horizontal component of the stretch scale factor which + * is applied before any other transformation property. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get stretchX():Number; + + /** + * @private + */ + function set stretchX(value:Number):void; + + /** + * The stretchY is the vertical component of the stretch scale factor which + * is applied before any other transformation property. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get stretchY():Number; + + /** + * @private + */ + function set stretchY(value:Number):void; + + //------------------------------------------------------------------------------ + + /** + * The computed matrix, calculated by combining the layout matrix and any offsets provided. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get computedMatrix():Matrix; + + /** + * The computed 3D matrix, calculated by combining the 3D layout matrix and any offsets provided. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 1.5 + * @productversion Flex 4.1 + */ + function get computedMatrix3D():Matrix3D; + } +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/46eaea76/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/MatrixUtil.as ---------------------------------------------------------------------- diff --git a/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/MatrixUtil.as b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/MatrixUtil.as new file mode 100644 index 0000000..04c2c13 --- /dev/null +++ b/frameworks/projects/Graphics/src/main/flex/org/apache/flex/core/graphics/utils/MatrixUtil.as @@ -0,0 +1,1605 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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 org.apache.flex.core.graphics.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; + + /** + * @private + * The MatrixUtil class is for internal use only. + * Class for matrix and geometric related math routines. + */ + public final class MatrixUtil + { + + private static const RADIANS_PER_DEGREES:Number = Math.PI / 180; + private static var SOLUTION_TOLERANCE:Number = 0.1; + private 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); + } + + } + +} +