blob: b29e300b59fa54e85bed77be29cc2e4c90190d15 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.<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*/);
}
}
}