//////////////////////////////////////////////////////////////////////////////// // // 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.geom { import __AS3__.vec.Vector; import flash.events.Event; import flash.geom.Matrix; import flash.geom.Matrix3D; import flash.geom.Point; import flash.geom.Vector3D; import mx.core.AdvancedLayoutFeatures; import mx.utils.MatrixUtil; /** * 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. = new Vector.(); 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. = _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. = _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. = _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*/); } } }