math.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /*globals svgedit*/
  2. /*jslint vars: true, eqeq: true */
  3. /**
  4. * Package: svedit.math
  5. *
  6. * Licensed under the MIT License
  7. *
  8. * Copyright(c) 2010 Alexis Deveria
  9. * Copyright(c) 2010 Jeff Schiller
  10. */
  11. // Dependencies:
  12. // None.
  13. /**
  14. * @typedef AngleCoord45
  15. * @type {object}
  16. * @property {number} x - The angle-snapped x value
  17. * @property {number} y - The angle-snapped y value
  18. * @property {number} a - The angle at which to snap
  19. */
  20. (function() {'use strict';
  21. if (!svgedit.math) {
  22. svgedit.math = {};
  23. }
  24. // Constants
  25. var NEAR_ZERO = 1e-14;
  26. // Throw away SVGSVGElement used for creating matrices/transforms.
  27. var svg = document.createElementNS(svgedit.NS.SVG, 'svg');
  28. /**
  29. * A (hopefully) quicker function to transform a point by a matrix
  30. * (this function avoids any DOM calls and just does the math)
  31. * @param {number} x - Float representing the x coordinate
  32. * @param {number} y - Float representing the y coordinate
  33. * @param {SVGMatrix} m - Matrix object to transform the point with
  34. * @returns {object} An x, y object representing the transformed point
  35. */
  36. svgedit.math.transformPoint = function (x, y, m) {
  37. return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f};
  38. };
  39. /**
  40. * Helper function to check if the matrix performs no actual transform
  41. * (i.e. exists for identity purposes)
  42. * @param {SVGMatrix} m - The matrix object to check
  43. * @returns {boolean} Indicates whether or not the matrix is 1,0,0,1,0,0
  44. */
  45. svgedit.math.isIdentity = function (m) {
  46. return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0);
  47. };
  48. /**
  49. * This function tries to return a SVGMatrix that is the multiplication m1*m2.
  50. * We also round to zero when it's near zero
  51. * @param {...SVGMatrix} matr - Two or more matrix objects to multiply
  52. * @returns {SVGMatrix} The matrix object resulting from the calculation
  53. */
  54. svgedit.math.matrixMultiply = function (matr) {
  55. var args = arguments, i = args.length, m = args[i-1];
  56. while (i-- > 1) {
  57. var m1 = args[i-1];
  58. m = m1.multiply(m);
  59. }
  60. if (Math.abs(m.a) < NEAR_ZERO) {m.a = 0;}
  61. if (Math.abs(m.b) < NEAR_ZERO) {m.b = 0;}
  62. if (Math.abs(m.c) < NEAR_ZERO) {m.c = 0;}
  63. if (Math.abs(m.d) < NEAR_ZERO) {m.d = 0;}
  64. if (Math.abs(m.e) < NEAR_ZERO) {m.e = 0;}
  65. if (Math.abs(m.f) < NEAR_ZERO) {m.f = 0;}
  66. return m;
  67. };
  68. /**
  69. * See if the given transformlist includes a non-indentity matrix transform
  70. * @param {object} [tlist] - The transformlist to check
  71. * @returns {boolean} Whether or not a matrix transform was found
  72. */
  73. svgedit.math.hasMatrixTransform = function (tlist) {
  74. if (!tlist) {return false;}
  75. var num = tlist.numberOfItems;
  76. while (num--) {
  77. var xform = tlist.getItem(num);
  78. if (xform.type == 1 && !svgedit.math.isIdentity(xform.matrix)) {return true;}
  79. }
  80. return false;
  81. };
  82. /**
  83. * Transforms a rectangle based on the given matrix
  84. * @param {number} l - Float with the box's left coordinate
  85. * @param {number} t - Float with the box's top coordinate
  86. * @param {number} w - Float with the box width
  87. * @param {number} h - Float with the box height
  88. * @param {SVGMatrix} m - Matrix object to transform the box by
  89. * @returns {object} An object with the following values:
  90. * tl - The top left coordinate (x,y object)
  91. * tr - The top right coordinate (x,y object)
  92. * bl - The bottom left coordinate (x,y object)
  93. * br - The bottom right coordinate (x,y object)
  94. * aabox - Object with the following values:
  95. * x - Float with the axis-aligned x coordinate
  96. * y - Float with the axis-aligned y coordinate
  97. * width - Float with the axis-aligned width coordinate
  98. * height - Float with the axis-aligned height coordinate
  99. */
  100. svgedit.math.transformBox = function (l, t, w, h, m) {
  101. var transformPoint = svgedit.math.transformPoint,
  102. tl = transformPoint(l, t, m),
  103. tr = transformPoint((l + w), t, m),
  104. bl = transformPoint(l, (t + h), m),
  105. br = transformPoint((l + w), (t + h), m),
  106. minx = Math.min(tl.x, tr.x, bl.x, br.x),
  107. maxx = Math.max(tl.x, tr.x, bl.x, br.x),
  108. miny = Math.min(tl.y, tr.y, bl.y, br.y),
  109. maxy = Math.max(tl.y, tr.y, bl.y, br.y);
  110. return {
  111. tl: tl,
  112. tr: tr,
  113. bl: bl,
  114. br: br,
  115. aabox: {
  116. x: minx,
  117. y: miny,
  118. width: (maxx - minx),
  119. height: (maxy - miny)
  120. }
  121. };
  122. };
  123. /**
  124. * This returns a single matrix Transform for a given Transform List
  125. * (this is the equivalent of SVGTransformList.consolidate() but unlike
  126. * that method, this one does not modify the actual SVGTransformList)
  127. * This function is very liberal with its min, max arguments
  128. * @param {object} tlist - The transformlist object
  129. * @param {integer} [min=0] - Optional integer indicating start transform position
  130. * @param {integer} [max] - Optional integer indicating end transform position;
  131. * defaults to one less than the tlist's numberOfItems
  132. * @returns {object} A single matrix transform object
  133. */
  134. svgedit.math.transformListToTransform = function (tlist, min, max) {
  135. if (tlist == null) {
  136. // Or should tlist = null have been prevented before this?
  137. return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
  138. }
  139. min = min || 0;
  140. max = max || (tlist.numberOfItems - 1);
  141. min = parseInt(min, 10);
  142. max = parseInt(max, 10);
  143. if (min > max) { var temp = max; max = min; min = temp; }
  144. var m = svg.createSVGMatrix();
  145. var i;
  146. for (i = min; i <= max; ++i) {
  147. // if our indices are out of range, just use a harmless identity matrix
  148. var mtom = (i >= 0 && i < tlist.numberOfItems ?
  149. tlist.getItem(i).matrix :
  150. svg.createSVGMatrix());
  151. m = svgedit.math.matrixMultiply(m, mtom);
  152. }
  153. return svg.createSVGTransformFromMatrix(m);
  154. };
  155. /**
  156. * Get the matrix object for a given element
  157. * @param {Element} elem - The DOM element to check
  158. * @returns {SVGMatrix} The matrix object associated with the element's transformlist
  159. */
  160. svgedit.math.getMatrix = function (elem) {
  161. var tlist = svgedit.transformlist.getTransformList(elem);
  162. return svgedit.math.transformListToTransform(tlist).matrix;
  163. };
  164. /**
  165. * Returns a 45 degree angle coordinate associated with the two given
  166. * coordinates
  167. * @param {number} x1 - First coordinate's x value
  168. * @param {number} x2 - Second coordinate's x value
  169. * @param {number} y1 - First coordinate's y value
  170. * @param {number} y2 - Second coordinate's y value
  171. * @returns {AngleCoord45}
  172. */
  173. svgedit.math.snapToAngle = function (x1, y1, x2, y2) {
  174. var snap = Math.PI / 4; // 45 degrees
  175. var dx = x2 - x1;
  176. var dy = y2 - y1;
  177. var angle = Math.atan2(dy, dx);
  178. var dist = Math.sqrt(dx * dx + dy * dy);
  179. var snapangle = Math.round(angle / snap) * snap;
  180. return {
  181. x: x1 + dist * Math.cos(snapangle),
  182. y: y1 + dist * Math.sin(snapangle),
  183. a: snapangle
  184. };
  185. };
  186. /**
  187. * Check if two rectangles (BBoxes objects) intersect each other
  188. * @param {SVGRect} r1 - The first BBox-like object
  189. * @param {SVGRect} r2 - The second BBox-like object
  190. * @returns {boolean} True if rectangles intersect
  191. */
  192. svgedit.math.rectsIntersect = function (r1, r2) {
  193. return r2.x < (r1.x + r1.width) &&
  194. (r2.x + r2.width) > r1.x &&
  195. r2.y < (r1.y + r1.height) &&
  196. (r2.y + r2.height) > r1.y;
  197. };
  198. }());