| /** |
| * 提供变换扩展 |
| * @module zrender/mixin/Transformable |
| * @author pissang (https://www.github.com/pissang) |
| */ |
| import * as matrix from '../core/matrix'; |
| import * as vector from '../core/vector'; |
| var mIdentity = matrix.identity; |
| var EPSILON = 5e-5; |
| |
| function isNotAroundZero(val) { |
| return val > EPSILON || val < -EPSILON; |
| } |
| /** |
| * @alias module:zrender/mixin/Transformable |
| * @constructor |
| */ |
| |
| |
| var Transformable = function (opts) { |
| opts = opts || {}; // If there are no given position, rotation, scale |
| |
| if (!opts.position) { |
| /** |
| * 平移 |
| * @type {Array.<number>} |
| * @default [0, 0] |
| */ |
| this.position = [0, 0]; |
| } |
| |
| if (opts.rotation == null) { |
| /** |
| * 旋转 |
| * @type {Array.<number>} |
| * @default 0 |
| */ |
| this.rotation = 0; |
| } |
| |
| if (!opts.scale) { |
| /** |
| * 缩放 |
| * @type {Array.<number>} |
| * @default [1, 1] |
| */ |
| this.scale = [1, 1]; |
| } |
| /** |
| * 旋转和缩放的原点 |
| * @type {Array.<number>} |
| * @default null |
| */ |
| |
| |
| this.origin = this.origin || null; |
| }; |
| |
| var transformableProto = Transformable.prototype; |
| transformableProto.transform = null; |
| /** |
| * 判断是否需要有坐标变换 |
| * 如果有坐标变换, 则从position, rotation, scale以及父节点的transform计算出自身的transform矩阵 |
| */ |
| |
| transformableProto.needLocalTransform = function () { |
| return isNotAroundZero(this.rotation) || isNotAroundZero(this.position[0]) || isNotAroundZero(this.position[1]) || isNotAroundZero(this.scale[0] - 1) || isNotAroundZero(this.scale[1] - 1); |
| }; |
| |
| var scaleTmp = []; |
| |
| transformableProto.updateTransform = function () { |
| var parent = this.parent; |
| var parentHasTransform = parent && parent.transform; |
| var needLocalTransform = this.needLocalTransform(); |
| var m = this.transform; |
| |
| if (!(needLocalTransform || parentHasTransform)) { |
| m && mIdentity(m); |
| return; |
| } |
| |
| m = m || matrix.create(); |
| |
| if (needLocalTransform) { |
| this.getLocalTransform(m); |
| } else { |
| mIdentity(m); |
| } // 应用父节点变换 |
| |
| |
| if (parentHasTransform) { |
| if (needLocalTransform) { |
| matrix.mul(m, parent.transform, m); |
| } else { |
| matrix.copy(m, parent.transform); |
| } |
| } // 保存这个变换矩阵 |
| |
| |
| this.transform = m; |
| var globalScaleRatio = this.globalScaleRatio; |
| |
| if (globalScaleRatio != null && globalScaleRatio !== 1) { |
| this.getGlobalScale(scaleTmp); |
| var relX = scaleTmp[0] < 0 ? -1 : 1; |
| var relY = scaleTmp[1] < 0 ? -1 : 1; |
| var sx = ((scaleTmp[0] - relX) * globalScaleRatio + relX) / scaleTmp[0] || 0; |
| var sy = ((scaleTmp[1] - relY) * globalScaleRatio + relY) / scaleTmp[1] || 0; |
| m[0] *= sx; |
| m[1] *= sx; |
| m[2] *= sy; |
| m[3] *= sy; |
| } |
| |
| this.invTransform = this.invTransform || matrix.create(); |
| matrix.invert(this.invTransform, m); |
| }; |
| |
| transformableProto.getLocalTransform = function (m) { |
| return Transformable.getLocalTransform(this, m); |
| }; |
| /** |
| * 将自己的transform应用到context上 |
| * @param {CanvasRenderingContext2D} ctx |
| */ |
| |
| |
| transformableProto.setTransform = function (ctx) { |
| var m = this.transform; |
| var dpr = ctx.dpr || 1; |
| |
| if (m) { |
| ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]); |
| } else { |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); |
| } |
| }; |
| |
| transformableProto.restoreTransform = function (ctx) { |
| var dpr = ctx.dpr || 1; |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); |
| }; |
| |
| var tmpTransform = []; |
| var originTransform = matrix.create(); |
| |
| transformableProto.setLocalTransform = function (m) { |
| if (!m) { |
| // TODO return or set identity? |
| return; |
| } |
| |
| var sx = m[0] * m[0] + m[1] * m[1]; |
| var sy = m[2] * m[2] + m[3] * m[3]; |
| var position = this.position; |
| var scale = this.scale; |
| |
| if (isNotAroundZero(sx - 1)) { |
| sx = Math.sqrt(sx); |
| } |
| |
| if (isNotAroundZero(sy - 1)) { |
| sy = Math.sqrt(sy); |
| } |
| |
| if (m[0] < 0) { |
| sx = -sx; |
| } |
| |
| if (m[3] < 0) { |
| sy = -sy; |
| } |
| |
| position[0] = m[4]; |
| position[1] = m[5]; |
| scale[0] = sx; |
| scale[1] = sy; |
| this.rotation = Math.atan2(-m[1] / sy, m[0] / sx); |
| }; |
| /** |
| * 分解`transform`矩阵到`position`, `rotation`, `scale` |
| */ |
| |
| |
| transformableProto.decomposeTransform = function () { |
| if (!this.transform) { |
| return; |
| } |
| |
| var parent = this.parent; |
| var m = this.transform; |
| |
| if (parent && parent.transform) { |
| // Get local transform and decompose them to position, scale, rotation |
| matrix.mul(tmpTransform, parent.invTransform, m); |
| m = tmpTransform; |
| } |
| |
| var origin = this.origin; |
| |
| if (origin && (origin[0] || origin[1])) { |
| originTransform[4] = origin[0]; |
| originTransform[5] = origin[1]; |
| matrix.mul(tmpTransform, m, originTransform); |
| tmpTransform[4] -= origin[0]; |
| tmpTransform[5] -= origin[1]; |
| m = tmpTransform; |
| } |
| |
| this.setLocalTransform(m); |
| }; |
| /** |
| * Get global scale |
| * @return {Array.<number>} |
| */ |
| |
| |
| transformableProto.getGlobalScale = function (out) { |
| var m = this.transform; |
| out = out || []; |
| |
| if (!m) { |
| out[0] = 1; |
| out[1] = 1; |
| return out; |
| } |
| |
| out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]); |
| out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]); |
| |
| if (m[0] < 0) { |
| out[0] = -out[0]; |
| } |
| |
| if (m[3] < 0) { |
| out[1] = -out[1]; |
| } |
| |
| return out; |
| }; |
| /** |
| * 变换坐标位置到 shape 的局部坐标空间 |
| * @method |
| * @param {number} x |
| * @param {number} y |
| * @return {Array.<number>} |
| */ |
| |
| |
| transformableProto.transformCoordToLocal = function (x, y) { |
| var v2 = [x, y]; |
| var invTransform = this.invTransform; |
| |
| if (invTransform) { |
| vector.applyTransform(v2, v2, invTransform); |
| } |
| |
| return v2; |
| }; |
| /** |
| * 变换局部坐标位置到全局坐标空间 |
| * @method |
| * @param {number} x |
| * @param {number} y |
| * @return {Array.<number>} |
| */ |
| |
| |
| transformableProto.transformCoordToGlobal = function (x, y) { |
| var v2 = [x, y]; |
| var transform = this.transform; |
| |
| if (transform) { |
| vector.applyTransform(v2, v2, transform); |
| } |
| |
| return v2; |
| }; |
| /** |
| * @static |
| * @param {Object} target |
| * @param {Array.<number>} target.origin |
| * @param {number} target.rotation |
| * @param {Array.<number>} target.position |
| * @param {Array.<number>} [m] |
| */ |
| |
| |
| Transformable.getLocalTransform = function (target, m) { |
| m = m || []; |
| mIdentity(m); |
| var origin = target.origin; |
| var scale = target.scale || [1, 1]; |
| var rotation = target.rotation || 0; |
| var position = target.position || [0, 0]; |
| |
| if (origin) { |
| // Translate to origin |
| m[4] -= origin[0]; |
| m[5] -= origin[1]; |
| } |
| |
| matrix.scale(m, m, scale); |
| |
| if (rotation) { |
| matrix.rotate(m, m, rotation); |
| } |
| |
| if (origin) { |
| // Translate back from origin |
| m[4] += origin[0]; |
| m[5] += origin[1]; |
| } |
| |
| m[4] += position[0]; |
| m[5] += position[1]; |
| return m; |
| }; |
| |
| export default Transformable; |