6004dd05290add7289fdf8893ed5e25d37d41efb5524dec83fe09c73b52171b77b7d4d73e8f24096c43a925589e5604be673ade297016ee873da10312d696c 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. import * as matrix from './matrix';
  2. import * as vector from './vector';
  3. const mIdentity = matrix.identity;
  4. const EPSILON = 5e-5;
  5. function isNotAroundZero(val: number) {
  6. return val > EPSILON || val < -EPSILON;
  7. }
  8. const scaleTmp: vector.VectorArray = [];
  9. const tmpTransform: matrix.MatrixArray = [];
  10. const originTransform = matrix.create();
  11. const abs = Math.abs;
  12. class Transformable {
  13. parent: Transformable
  14. x: number
  15. y: number
  16. scaleX: number
  17. scaleY: number
  18. skewX: number
  19. skewY: number
  20. rotation: number
  21. /**
  22. * Will translated the element to the anchor position before applying other transforms.
  23. */
  24. anchorX: number
  25. anchorY: number
  26. /**
  27. * Origin of scale, rotation, skew
  28. */
  29. originX: number
  30. originY: number
  31. /**
  32. * Scale ratio
  33. */
  34. globalScaleRatio: number
  35. transform: matrix.MatrixArray
  36. invTransform: matrix.MatrixArray
  37. /**
  38. * Get computed local transform
  39. */
  40. getLocalTransform(m?: matrix.MatrixArray) {
  41. return Transformable.getLocalTransform(this, m);
  42. }
  43. /**
  44. * Set position from array
  45. */
  46. setPosition(arr: number[]) {
  47. this.x = arr[0];
  48. this.y = arr[1];
  49. }
  50. /**
  51. * Set scale from array
  52. */
  53. setScale(arr: number[]) {
  54. this.scaleX = arr[0];
  55. this.scaleY = arr[1];
  56. }
  57. /**
  58. * Set skew from array
  59. */
  60. setSkew(arr: number[]) {
  61. this.skewX = arr[0];
  62. this.skewY = arr[1];
  63. }
  64. /**
  65. * Set origin from array
  66. */
  67. setOrigin(arr: number[]) {
  68. this.originX = arr[0];
  69. this.originY = arr[1];
  70. }
  71. /**
  72. * If needs to compute transform
  73. */
  74. needLocalTransform(): boolean {
  75. return isNotAroundZero(this.rotation)
  76. || isNotAroundZero(this.x)
  77. || isNotAroundZero(this.y)
  78. || isNotAroundZero(this.scaleX - 1)
  79. || isNotAroundZero(this.scaleY - 1)
  80. || isNotAroundZero(this.skewX)
  81. || isNotAroundZero(this.skewY);
  82. }
  83. /**
  84. * Update global transform
  85. */
  86. updateTransform() {
  87. const parentTransform = this.parent && this.parent.transform;
  88. const needLocalTransform = this.needLocalTransform();
  89. let m = this.transform;
  90. if (!(needLocalTransform || parentTransform)) {
  91. m && mIdentity(m);
  92. return;
  93. }
  94. m = m || matrix.create();
  95. if (needLocalTransform) {
  96. this.getLocalTransform(m);
  97. }
  98. else {
  99. mIdentity(m);
  100. }
  101. // 应用父节点变换
  102. if (parentTransform) {
  103. if (needLocalTransform) {
  104. matrix.mul(m, parentTransform, m);
  105. }
  106. else {
  107. matrix.copy(m, parentTransform);
  108. }
  109. }
  110. // 保存这个变换矩阵
  111. this.transform = m;
  112. this._resolveGlobalScaleRatio(m);
  113. }
  114. private _resolveGlobalScaleRatio(m: matrix.MatrixArray) {
  115. const globalScaleRatio = this.globalScaleRatio;
  116. if (globalScaleRatio != null && globalScaleRatio !== 1) {
  117. this.getGlobalScale(scaleTmp);
  118. const relX = scaleTmp[0] < 0 ? -1 : 1;
  119. const relY = scaleTmp[1] < 0 ? -1 : 1;
  120. const sx = ((scaleTmp[0] - relX) * globalScaleRatio + relX) / scaleTmp[0] || 0;
  121. const sy = ((scaleTmp[1] - relY) * globalScaleRatio + relY) / scaleTmp[1] || 0;
  122. m[0] *= sx;
  123. m[1] *= sx;
  124. m[2] *= sy;
  125. m[3] *= sy;
  126. }
  127. this.invTransform = this.invTransform || matrix.create();
  128. matrix.invert(this.invTransform, m);
  129. }
  130. /**
  131. * Get computed global transform
  132. * NOTE: this method will force update transform on all ancestors.
  133. * Please be aware of the potential performance cost.
  134. */
  135. getComputedTransform() {
  136. let transformNode: Transformable = this;
  137. const ancestors: Transformable[] = [];
  138. while (transformNode) {
  139. ancestors.push(transformNode);
  140. transformNode = transformNode.parent;
  141. }
  142. // Update from topdown.
  143. while (transformNode = ancestors.pop()) {
  144. transformNode.updateTransform();
  145. }
  146. return this.transform;
  147. }
  148. setLocalTransform(m: vector.VectorArray) {
  149. if (!m) {
  150. // TODO return or set identity?
  151. return;
  152. }
  153. let sx = m[0] * m[0] + m[1] * m[1];
  154. let sy = m[2] * m[2] + m[3] * m[3];
  155. const rotation = Math.atan2(m[1], m[0]);
  156. const shearX = Math.PI / 2 + rotation - Math.atan2(m[3], m[2]);
  157. sy = Math.sqrt(sy) * Math.cos(shearX);
  158. sx = Math.sqrt(sx);
  159. this.skewX = shearX;
  160. this.skewY = 0;
  161. this.rotation = -rotation;
  162. this.x = +m[4];
  163. this.y = +m[5];
  164. this.scaleX = sx;
  165. this.scaleY = sy;
  166. this.originX = 0;
  167. this.originY = 0;
  168. }
  169. /**
  170. * 分解`transform`矩阵到`position`, `rotation`, `scale`
  171. */
  172. decomposeTransform() {
  173. if (!this.transform) {
  174. return;
  175. }
  176. const parent = this.parent;
  177. let m = this.transform;
  178. if (parent && parent.transform) {
  179. // Get local transform and decompose them to position, scale, rotation
  180. matrix.mul(tmpTransform, parent.invTransform, m);
  181. m = tmpTransform;
  182. }
  183. const ox = this.originX;
  184. const oy = this.originY;
  185. if (ox || oy) {
  186. originTransform[4] = ox;
  187. originTransform[5] = oy;
  188. matrix.mul(tmpTransform, m, originTransform);
  189. tmpTransform[4] -= ox;
  190. tmpTransform[5] -= oy;
  191. m = tmpTransform;
  192. }
  193. this.setLocalTransform(m);
  194. }
  195. /**
  196. * Get global scale
  197. */
  198. getGlobalScale(out?: vector.VectorArray): vector.VectorArray {
  199. const m = this.transform;
  200. out = out || [];
  201. if (!m) {
  202. out[0] = 1;
  203. out[1] = 1;
  204. return out;
  205. }
  206. out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
  207. out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
  208. if (m[0] < 0) {
  209. out[0] = -out[0];
  210. }
  211. if (m[3] < 0) {
  212. out[1] = -out[1];
  213. }
  214. return out;
  215. }
  216. /**
  217. * 变换坐标位置到 shape 的局部坐标空间
  218. */
  219. transformCoordToLocal(x: number, y: number): number[] {
  220. const v2 = [x, y];
  221. const invTransform = this.invTransform;
  222. if (invTransform) {
  223. vector.applyTransform(v2, v2, invTransform);
  224. }
  225. return v2;
  226. }
  227. /**
  228. * 变换局部坐标位置到全局坐标空间
  229. */
  230. transformCoordToGlobal(x: number, y: number): number[] {
  231. const v2 = [x, y];
  232. const transform = this.transform;
  233. if (transform) {
  234. vector.applyTransform(v2, v2, transform);
  235. }
  236. return v2;
  237. }
  238. getLineScale() {
  239. const m = this.transform;
  240. // Get the line scale.
  241. // Determinant of `m` means how much the area is enlarged by the
  242. // transformation. So its square root can be used as a scale factor
  243. // for width.
  244. return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10
  245. ? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1]))
  246. : 1;
  247. }
  248. copyTransform(source: Transformable) {
  249. copyTransform(this, source);
  250. }
  251. static getLocalTransform(target: Transformable, m?: matrix.MatrixArray): matrix.MatrixArray {
  252. m = m || [];
  253. const ox = target.originX || 0;
  254. const oy = target.originY || 0;
  255. const sx = target.scaleX;
  256. const sy = target.scaleY;
  257. const ax = target.anchorX;
  258. const ay = target.anchorY;
  259. const rotation = target.rotation || 0;
  260. const x = target.x;
  261. const y = target.y;
  262. const skewX = target.skewX ? Math.tan(target.skewX) : 0;
  263. // TODO: zrender use different hand in coordinate system and y axis is inversed.
  264. const skewY = target.skewY ? Math.tan(-target.skewY) : 0;
  265. // The order of transform (-anchor * -origin * scale * skew * rotate * origin * translate).
  266. // We merge (-origin * scale * skew) into one. Also did identity in these operations.
  267. // origin
  268. if (ox || oy || ax || ay) {
  269. const dx = ox + ax;
  270. const dy = oy + ay;
  271. m[4] = -dx * sx - skewX * dy * sy;
  272. m[5] = -dy * sy - skewY * dx * sx;
  273. }
  274. else {
  275. m[4] = m[5] = 0;
  276. }
  277. // scale
  278. m[0] = sx;
  279. m[3] = sy;
  280. // skew
  281. m[1] = skewY * sx;
  282. m[2] = skewX * sy;
  283. // Apply rotation
  284. rotation && matrix.rotate(m, m, rotation);
  285. // Translate back from origin and apply translation
  286. m[4] += ox + x;
  287. m[5] += oy + y;
  288. return m;
  289. }
  290. private static initDefaultProps = (function () {
  291. const proto = Transformable.prototype;
  292. proto.scaleX =
  293. proto.scaleY =
  294. proto.globalScaleRatio = 1;
  295. proto.x =
  296. proto.y =
  297. proto.originX =
  298. proto.originY =
  299. proto.skewX =
  300. proto.skewY =
  301. proto.rotation =
  302. proto.anchorX =
  303. proto.anchorY = 0;
  304. })()
  305. };
  306. export const TRANSFORMABLE_PROPS = [
  307. 'x', 'y', 'originX', 'originY', 'anchorX', 'anchorY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY'
  308. ] as const;
  309. export type TransformProp = (typeof TRANSFORMABLE_PROPS)[number]
  310. export function copyTransform(
  311. target: Partial<Pick<Transformable, TransformProp>>,
  312. source: Pick<Transformable, TransformProp>
  313. ) {
  314. for (let i = 0; i < TRANSFORMABLE_PROPS.length; i++) {
  315. const propName = TRANSFORMABLE_PROPS[i];
  316. target[propName] = source[propName];
  317. }
  318. }
  319. export default Transformable;