refactor transform processing
This commit is contained in:
Horcrux
2016-07-27 10:43:24 +08:00
parent 1051bc078f
commit a636211917
5 changed files with 370 additions and 174 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ class Shape extends Component {
this.state = this.touchableGetInitialState();
}
extractProps = (props, options = {stroke: true, fill: true, responder: true}) => {
extractProps = (props, options = {stroke: true, transform: true, fill: true, responder: true}) => {
let extractedProps = extractProps(props, options);
if (extractedProps.touchable && !extractedProps.disabled) {
_.assign(extractedProps, {
+285
View File
@@ -0,0 +1,285 @@
/**
* based on
* https://github.com/CreateJS/EaselJS/blob/631cdffb85eff9413dab43b4676f059b4232d291/src/easeljs/geom/Matrix2D.js
*/
const DEG_TO_RAD = Math.PI/180;
/**
* Represents an affine transformation matrix, and provides tools for constructing and concatenating matrices.
*
* This matrix can be visualized as:
*
* [ a c tx
* b d ty
* 0 0 1 ]
*
* Note the locations of b and c.
*
* @class Matrix2D
* @param {Number} [a=1] Specifies the a property for the new matrix.
* @param {Number} [b=0] Specifies the b property for the new matrix.
* @param {Number} [c=0] Specifies the c property for the new matrix.
* @param {Number} [d=1] Specifies the d property for the new matrix.
* @param {Number} [tx=0] Specifies the tx property for the new matrix.
* @param {Number} [ty=0] Specifies the ty property for the new matrix.
* @constructor
**/
class Matrix2D {
constructor(a, b, c, d, tx, ty) {
this.setTransform(a, b, c, d, tx, ty);
// public properties:
// assigned in the setValues method.
/**
* Position (0, 0) in a 3x3 affine transformation matrix.
* @property a
* @type Number
**/
/**
* Position (0, 1) in a 3x3 affine transformation matrix.
* @property b
* @type Number
**/
/**
* Position (1, 0) in a 3x3 affine transformation matrix.
* @property c
* @type Number
**/
/**
* Position (1, 1) in a 3x3 affine transformation matrix.
* @property d
* @type Number
**/
/**
* Position (2, 0) in a 3x3 affine transformation matrix.
* @property tx
* @type Number
**/
/**
* Position (2, 1) in a 3x3 affine transformation matrix.
* @property ty
* @type Number
**/
}
/**
* Set current matrix to new absolute matrix.
* @method setTransform
* @param {Number} [a=1] Specifies the a property for the new matrix.
* @param {Number} [b=0] Specifies the b property for the new matrix.
* @param {Number} [c=0] Specifies the c property for the new matrix.
* @param {Number} [d=1] Specifies the d property for the new matrix.
* @param {Number} [tx=0] Specifies the tx property for the new matrix.
* @param {Number} [ty=0] Specifies the ty property for the new matrix.
* @return {Matrix2D} This instance. Useful for chaining method calls.
*/
setTransform = function(a, b, c, d, tx, ty) {
// don't forget to update docs in the constructor if these change:
this.a = (a == null) ? 1 : a;
this.b = b || 0;
this.c = c || 0;
this.d = (d == null) ? 1 : d;
this.tx = tx || 0;
this.ty = ty || 0;
return this;
};
/**
* Reset current matrix to an identity matrix.
* @method reset
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
reset = function() {
this.a = this.d = 1;
this.b = this.c = this.tx = this.ty = 0;
return this;
};
/**
* Returns an array with current matrix values.
* @method toArray
* @return {Array} an array with current matrix values.
**/
toArray = function() {
return [this.a, this.b, this.c, this.d, this.tx, this.ty];
};
/**
* Copies all properties from the specified matrix to this matrix.
* @method copy
* @param {Matrix2D} matrix The matrix to copy properties from.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
*/
copy = function(matrix) {
return this.setTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
};
/**
* Clones current instance and returning a new matrix.
* @method clone
* @return {Matrix2D} a clone of the Matrix2D instance.
**/
clone = function() {
return new Matrix2D(this.a, this.b, this.c, this.d, this.tx, this.ty);
};
/**
* Prepends the specified matrix properties to this matrix.
* This is the equivalent of multiplying `(specified matrix) * (this matrix)`.
* All parameters are required.
* @method prepend
* @param {Number} a
* @param {Number} b
* @param {Number} c
* @param {Number} d
* @param {Number} tx
* @param {Number} ty
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
prepend = function(a, b, c, d, tx, ty) {
var a1 = this.a;
var c1 = this.c;
var tx1 = this.tx;
this.a = a*a1+c*this.b;
this.b = b*a1+d*this.b;
this.c = a*c1+c*this.d;
this.d = b*c1+d*this.d;
this.tx = a*tx1+c*this.ty+tx;
this.ty = b*tx1+d*this.ty+ty;
return this;
};
/**
* Appends the specified matrix properties to this matrix. All parameters are required.
* This is the equivalent of multiplying `(this matrix) * (specified matrix)`.
* @method append
* @param {Number} a
* @param {Number} b
* @param {Number} c
* @param {Number} d
* @param {Number} tx
* @param {Number} ty
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
append = function(a, b, c, d, tx, ty) {
var a1 = this.a;
var b1 = this.b;
var c1 = this.c;
var d1 = this.d;
if (a != 1 || b != 0 || c != 0 || d != 1) {
this.a = a1*a+c1*b;
this.b = b1*a+d1*b;
this.c = a1*c+c1*d;
this.d = b1*c+d1*d;
}
this.tx = a1*tx+c1*ty+this.tx;
this.ty = b1*tx+d1*ty+this.ty;
return this;
};
/**
* Generates matrix properties from the specified display object transform properties, and appends them to this matrix.
* For example, you can use this to generate a matrix representing the transformations of a display object:
*
* var mtx = new createjs.Matrix2D();
* mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation);
* @method appendTransform
* @param {Number} x
* @param {Number} y
* @param {Number} scaleX
* @param {Number} scaleY
* @param {Number} rotation
* @param {Number} skewX
* @param {Number} skewY
* @param {Number} regX Optional.
* @param {Number} regY Optional.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
appendTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {
if (rotation%360) {
var r = rotation*DEG_TO_RAD;
var cos = Math.cos(r);
var sin = Math.sin(r);
} else {
cos = 1;
sin = 0;
}
if (skewX || skewY) {
// TODO: can this be combined into a single append operation?
skewX *= DEG_TO_RAD;
skewY *= DEG_TO_RAD;
this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y);
this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0);
} else {
this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y);
}
if (regX || regY) {
// append the registration offset:
this.tx -= regX*this.a+regY*this.c;
this.ty -= regX*this.b+regY*this.d;
}
return this;
};
/**
* Generates matrix properties from the specified display object transform properties, and prepends them to this matrix.
* For example, you could calculate the combined transformation for a child object using:
*
* var o = myDisplayObject;
* var mtx = new createjs.Matrix2D();
* do {
* // prepend each parent's transformation in turn:
* mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY);
* } while (o = o.parent);
*
* Note that the above example would not account for {{#crossLink "DisplayObject/transformMatrix:property"}}{{/crossLink}}
* values. See {{#crossLink "Matrix2D/prependMatrix"}}{{/crossLink}} for an example that does.
* @method prependTransform
* @param {Number} x
* @param {Number} y
* @param {Number} scaleX
* @param {Number} scaleY
* @param {Number} rotation
* @param {Number} skewX
* @param {Number} skewY
* @param {Number} regX Optional.
* @param {Number} regY Optional.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {
if (rotation%360) {
var r = rotation*DEG_TO_RAD;
var cos = Math.cos(r);
var sin = Math.sin(r);
} else {
cos = 1;
sin = 0;
}
if (regX || regY) {
// prepend the registration offset:
this.tx -= regX; this.ty -= regY;
}
if (skewX || skewY) {
// TODO: can this be combined into a single prepend operation?
skewX *= DEG_TO_RAD;
skewY *= DEG_TO_RAD;
this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0);
this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y);
} else {
this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y);
}
return this;
};
}
export default Matrix2D;
-137
View File
@@ -1,137 +0,0 @@
import _ from 'lodash';
export default class {
constructor(xx, yx, xy, yy, x, y){
if (xx && typeof xx === 'object'){
yx = xx.yx; yy = xx.yy; y = xx.y;
xy = xx.xy; x = xx.x; xx = xx.xx;
}
this.xx = _.isNil(xx) ? 1 : xx;
this.yx = yx || 0;
this.xy = xy || 0;
this.yy = _.isNil(yy) ? 1 : yy;
this.x = (_.isNil(x) ? this.x : x) || 0;
this.y = (_.isNil(y) ? this.y : y) || 0;
return this;
}
xx = 1;
yx = 0;
x = 0;
xy = 0;
yy = 1;
y = 0;
transform = (xx, yx, xy, yy, x, y) => {
if (xx && typeof xx === 'object'){
yx = xx.yx; yy = xx.yy; y = xx.y;
xy = xx.xy; x = xx.x; xx = xx.xx;
}
if (!x) {
x = 0;
}
if (!y) {
y = 0;
}
return this.transformTo(
this.xx * xx + this.xy * yx,
this.yx * xx + this.yy * yx,
this.xx * xy + this.xy * yy,
this.yx * xy + this.yy * yy,
this.xx * x + this.xy * y + this.x,
this.yx * x + this.yy * y + this.y
);
};
transformTo = this.constructor;
translate = (x, y) => {
return this.transform(1, 0, 0, 1, x, y);
};
move = (x, y) => {
this.x += x || 0;
this.y += y || 0;
return this;
};
scale = (x, y) => {
return this.transform(x, 0, 0, _.isNil(y) ? x : y, 0, 0);
};
rotate = (deg, x, y) => {
if (_.isNil(x) || _.isNil(y)){
x = (this.left || 0) + (this.width || 0) / 2;
y = (this.top || 0) + (this.height || 0) / 2;
}
var rad = deg * Math.PI / 180, sin = Math.sin(rad), cos = Math.cos(rad);
this.transform(1, 0, 0, 1, x, y);
return this.transformTo(
cos * this.xx - sin * this.yx,
sin * this.xx + cos * this.yx,
cos * this.xy - sin * this.yy,
sin * this.xy + cos * this.yy,
this.x,
this.y
).transform(1, 0, 0, 1, -x, -y);
};
moveTo = (x, y) => {
return this.transformTo(this.xx, this.yx, this.xy, this.yy, x, y);
};
rotateTo = (deg, x, y) => {
let flip = this.yx / this.xx > this.yy / this.xy ? -1 : 1;
if (this.xx < 0 ? this.xy >= 0 : this.xy < 0) {
flip = -flip;
}
return this.rotate(deg - Math.atan2(flip * this.yx, flip * this.xx) * 180 / Math.PI, x, y);
};
scaleTo = (x, y) => {
// Normalize
var h = Math.sqrt(this.xx * this.xx + this.yx * this.yx);
this.xx /= h;
this.yx /= h;
h = Math.sqrt(this.yy * this.yy + this.xy * this.xy);
this.yy /= h;
this.xy /= h;
return this.scale(x, y);
};
resizeTo = (width, height) => {
var w = this.width, h = this.height;
if (!w || !h) {
return this;
}
return this.scaleTo(width / w, height / h);
};
inversePoint = (x, y) => {
var a = this.xx, b = this.yx,
c = this.xy, d = this.yy,
e = this.x, f = this.y;
var det = b * c - a * d;
if (det === 0) {
return null;
}
return {
x: (d * (e - x) + c * (y - f)) / det,
y: (a * (f - y) + b * (x - e)) / det
};
};
point = (x, y) => {
return {
x: this.xx * x + this.xy * y + this.x,
y: this.yx * x + this.yy * y + this.y
};
};
}
+75 -35
View File
@@ -1,45 +1,85 @@
import Transform from '../Transform';
import Matrix2D from '../Matrix2D';
import _ from 'lodash';
let pooledTransform = new Transform();
let pooledMatrix = new Matrix2D();
function transformToMatrix(props) {
let scaleX = !_.isNil(props.scaleX) ? props.scaleX :
!_.isNil(props.scale) ? props.scale : 1;
let scaleY = !_.isNil(props.scaleY) ? props.scaleY :
!_.isNil(props.scale) ? props.scale : 1;
function transformToMatrix(props, transform) {
pooledMatrix.reset();
appendTransform(props);
pooledTransform
.transformTo(1, 0, 0, 1, 0, 0)
.move(props.x || 0, props.y || 0)
.rotate(props.rotation || 0, props.originX, props.originY)
.scale(scaleX, scaleY, props.originX, props.originY);
if (!_.isNil(props.transform)) {
pooledTransform.transform(props.transform);
if (transform) {
appendTransform(transform);
}
return [
pooledTransform.xx, pooledTransform.yx,
pooledTransform.xy, pooledTransform.yy,
pooledTransform.x, pooledTransform.y
];
return pooledMatrix.toArray();
}
function appendTransform(transform) {
pooledMatrix
.appendTransform(
transform.x + transform.originX,
transform.y + transform.originY,
transform.scaleX, transform.scaleY,
transform.rotation,
transform.skewX,
transform.skewY,
transform.originX,
transform.originY
);
}
function universal2axis(universal, axisX, axisY, defaultValue) {
let coords = [];
let x;
let y;
if (_.isString(universal)) {
coords = universal.split(/\s*,\s*/);
if (coords.length === 2) {
x = +coords[0];
y = +coords[1];
} else if (coords.length === 1) {
x = y = +coords[0];
}
} else if (_.isNumber(universal)) {
x = y = universal;
}
axisX = +axisX;
if (!isNaN(axisX)) {
x = axisX;
}
axisY = +axisY;
if (!isNaN(axisY)) {
y = axisY;
}
return [x || defaultValue || 0, y || defaultValue || 0];
}
function props2transform(props) {
let [originX, originY] = universal2axis(props.origin, props.originX, props.originY);
let [scaleX, scaleY] = universal2axis(props.scale, props.scaleX, props.scaleY, 1);
let [skewX, skewY] = universal2axis(props.skew, props.skewX, props.skewY);
let [translateX, translateY] = universal2axis(props.translate, props.translateX, props.translateY);
return {
rotation: +props.rotation || +props.rotate || 0,
scaleX: scaleX,
scaleY: scaleY,
originX: originX,
originY: originY,
skewX: skewX,
skewY: skewY,
x: translateX,
y: translateY
};
}
/**
*
* @param props
*/
export default function (props) {
let coords = props.origin ? props.origin.split(/\s*,\s*/) : [];
// TODO: support Percentage for originX,originY
let originX = coords.length === 2 ? coords[0] : props.originX;
let originY = coords.length === 2 ? coords[1] : props.originY;
return transformToMatrix({
rotation: +props.rotation / 2 || +props.rotate / 2 || 0,
scale: props.scale,
scaleX: props.scaleX,
scaleY: props.scaleY,
originX: +originX || 0,
originY: +originY || 0,
x: +props.x || 0,
y: +props.y || 0
});
// TODO: support Percentage for transform
return transformToMatrix(props2transform(props), props.transform ? props2transform(props.transform) : null);
}
+9 -1
View File
@@ -57,11 +57,19 @@ const transformProps = {
scaleX: numberProp,
scaleY: numberProp,
rotate: numberProp,
rotation: numberProp,
translate: numberProp,
translateX: numberProp,
translateY: numberProp,
x: numberProp,
y: numberProp,
origin: numberProp,
originX: numberProp,
originY: numberProp,
transform: PropTypes.string
skew: numberProp,
skewX: numberProp,
skewY: numberProp,
transform: PropTypes.object
};
const pathProps = {