bez.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import {
  2. bmPow,
  3. bmFloor,
  4. bmSqrt,
  5. getDefaultCurveSegments,
  6. } from './common';
  7. import {
  8. createSizedArray,
  9. createTypedArray,
  10. } from './helpers/arrays';
  11. import segmentsLengthPool from './pooling/segments_length_pool';
  12. import bezierLengthPool from './pooling/bezier_length_pool';
  13. function bezFunction() {
  14. var math = Math;
  15. function pointOnLine2D(x1, y1, x2, y2, x3, y3) {
  16. var det1 = (x1 * y2) + (y1 * x3) + (x2 * y3) - (x3 * y2) - (y3 * x1) - (x2 * y1);
  17. return det1 > -0.001 && det1 < 0.001;
  18. }
  19. function pointOnLine3D(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
  20. if (z1 === 0 && z2 === 0 && z3 === 0) {
  21. return pointOnLine2D(x1, y1, x2, y2, x3, y3);
  22. }
  23. var dist1 = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) + math.pow(z2 - z1, 2));
  24. var dist2 = math.sqrt(math.pow(x3 - x1, 2) + math.pow(y3 - y1, 2) + math.pow(z3 - z1, 2));
  25. var dist3 = math.sqrt(math.pow(x3 - x2, 2) + math.pow(y3 - y2, 2) + math.pow(z3 - z2, 2));
  26. var diffDist;
  27. if (dist1 > dist2) {
  28. if (dist1 > dist3) {
  29. diffDist = dist1 - dist2 - dist3;
  30. } else {
  31. diffDist = dist3 - dist2 - dist1;
  32. }
  33. } else if (dist3 > dist2) {
  34. diffDist = dist3 - dist2 - dist1;
  35. } else {
  36. diffDist = dist2 - dist1 - dist3;
  37. }
  38. return diffDist > -0.0001 && diffDist < 0.0001;
  39. }
  40. var getBezierLength = (function () {
  41. return function (pt1, pt2, pt3, pt4) {
  42. var curveSegments = getDefaultCurveSegments();
  43. var k;
  44. var i;
  45. var len;
  46. var ptCoord;
  47. var perc;
  48. var addedLength = 0;
  49. var ptDistance;
  50. var point = [];
  51. var lastPoint = [];
  52. var lengthData = bezierLengthPool.newElement();
  53. len = pt3.length;
  54. for (k = 0; k < curveSegments; k += 1) {
  55. perc = k / (curveSegments - 1);
  56. ptDistance = 0;
  57. for (i = 0; i < len; i += 1) {
  58. ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * pt3[i] + 3 * (1 - perc) * bmPow(perc, 2) * pt4[i] + bmPow(perc, 3) * pt2[i];
  59. point[i] = ptCoord;
  60. if (lastPoint[i] !== null) {
  61. ptDistance += bmPow(point[i] - lastPoint[i], 2);
  62. }
  63. lastPoint[i] = point[i];
  64. }
  65. if (ptDistance) {
  66. ptDistance = bmSqrt(ptDistance);
  67. addedLength += ptDistance;
  68. }
  69. lengthData.percents[k] = perc;
  70. lengthData.lengths[k] = addedLength;
  71. }
  72. lengthData.addedLength = addedLength;
  73. return lengthData;
  74. };
  75. }());
  76. function getSegmentsLength(shapeData) {
  77. var segmentsLength = segmentsLengthPool.newElement();
  78. var closed = shapeData.c;
  79. var pathV = shapeData.v;
  80. var pathO = shapeData.o;
  81. var pathI = shapeData.i;
  82. var i;
  83. var len = shapeData._length;
  84. var lengths = segmentsLength.lengths;
  85. var totalLength = 0;
  86. for (i = 0; i < len - 1; i += 1) {
  87. lengths[i] = getBezierLength(pathV[i], pathV[i + 1], pathO[i], pathI[i + 1]);
  88. totalLength += lengths[i].addedLength;
  89. }
  90. if (closed && len) {
  91. lengths[i] = getBezierLength(pathV[i], pathV[0], pathO[i], pathI[0]);
  92. totalLength += lengths[i].addedLength;
  93. }
  94. segmentsLength.totalLength = totalLength;
  95. return segmentsLength;
  96. }
  97. function BezierData(length) {
  98. this.segmentLength = 0;
  99. this.points = new Array(length);
  100. }
  101. function PointData(partial, point) {
  102. this.partialLength = partial;
  103. this.point = point;
  104. }
  105. var buildBezierData = (function () {
  106. var storedData = {};
  107. return function (pt1, pt2, pt3, pt4) {
  108. var bezierName = (pt1[0] + '_' + pt1[1] + '_' + pt2[0] + '_' + pt2[1] + '_' + pt3[0] + '_' + pt3[1] + '_' + pt4[0] + '_' + pt4[1]).replace(/\./g, 'p');
  109. if (!storedData[bezierName]) {
  110. var curveSegments = getDefaultCurveSegments();
  111. var k;
  112. var i;
  113. var len;
  114. var ptCoord;
  115. var perc;
  116. var addedLength = 0;
  117. var ptDistance;
  118. var point;
  119. var lastPoint = null;
  120. if (pt1.length === 2 && (pt1[0] !== pt2[0] || pt1[1] !== pt2[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt1[0] + pt3[0], pt1[1] + pt3[1]) && pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt2[0] + pt4[0], pt2[1] + pt4[1])) {
  121. curveSegments = 2;
  122. }
  123. var bezierData = new BezierData(curveSegments);
  124. len = pt3.length;
  125. for (k = 0; k < curveSegments; k += 1) {
  126. point = createSizedArray(len);
  127. perc = k / (curveSegments - 1);
  128. ptDistance = 0;
  129. for (i = 0; i < len; i += 1) {
  130. ptCoord = bmPow(1 - perc, 3) * pt1[i] + 3 * bmPow(1 - perc, 2) * perc * (pt1[i] + pt3[i]) + 3 * (1 - perc) * bmPow(perc, 2) * (pt2[i] + pt4[i]) + bmPow(perc, 3) * pt2[i];
  131. point[i] = ptCoord;
  132. if (lastPoint !== null) {
  133. ptDistance += bmPow(point[i] - lastPoint[i], 2);
  134. }
  135. }
  136. ptDistance = bmSqrt(ptDistance);
  137. addedLength += ptDistance;
  138. bezierData.points[k] = new PointData(ptDistance, point);
  139. lastPoint = point;
  140. }
  141. bezierData.segmentLength = addedLength;
  142. storedData[bezierName] = bezierData;
  143. }
  144. return storedData[bezierName];
  145. };
  146. }());
  147. function getDistancePerc(perc, bezierData) {
  148. var percents = bezierData.percents;
  149. var lengths = bezierData.lengths;
  150. var len = percents.length;
  151. var initPos = bmFloor((len - 1) * perc);
  152. var lengthPos = perc * bezierData.addedLength;
  153. var lPerc = 0;
  154. if (initPos === len - 1 || initPos === 0 || lengthPos === lengths[initPos]) {
  155. return percents[initPos];
  156. }
  157. var dir = lengths[initPos] > lengthPos ? -1 : 1;
  158. var flag = true;
  159. while (flag) {
  160. if (lengths[initPos] <= lengthPos && lengths[initPos + 1] > lengthPos) {
  161. lPerc = (lengthPos - lengths[initPos]) / (lengths[initPos + 1] - lengths[initPos]);
  162. flag = false;
  163. } else {
  164. initPos += dir;
  165. }
  166. if (initPos < 0 || initPos >= len - 1) {
  167. // FIX for TypedArrays that don't store floating point values with enough accuracy
  168. if (initPos === len - 1) {
  169. return percents[initPos];
  170. }
  171. flag = false;
  172. }
  173. }
  174. return percents[initPos] + (percents[initPos + 1] - percents[initPos]) * lPerc;
  175. }
  176. function getPointInSegment(pt1, pt2, pt3, pt4, percent, bezierData) {
  177. var t1 = getDistancePerc(percent, bezierData);
  178. var u1 = 1 - t1;
  179. var ptX = math.round((u1 * u1 * u1 * pt1[0] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[0] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[0] + t1 * t1 * t1 * pt2[0]) * 1000) / 1000;
  180. var ptY = math.round((u1 * u1 * u1 * pt1[1] + (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[1] + (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[1] + t1 * t1 * t1 * pt2[1]) * 1000) / 1000;
  181. return [ptX, ptY];
  182. }
  183. var bezierSegmentPoints = createTypedArray('float32', 8);
  184. function getNewSegment(pt1, pt2, pt3, pt4, startPerc, endPerc, bezierData) {
  185. if (startPerc < 0) {
  186. startPerc = 0;
  187. } else if (startPerc > 1) {
  188. startPerc = 1;
  189. }
  190. var t0 = getDistancePerc(startPerc, bezierData);
  191. endPerc = endPerc > 1 ? 1 : endPerc;
  192. var t1 = getDistancePerc(endPerc, bezierData);
  193. var i;
  194. var len = pt1.length;
  195. var u0 = 1 - t0;
  196. var u1 = 1 - t1;
  197. var u0u0u0 = u0 * u0 * u0;
  198. var t0u0u0_3 = t0 * u0 * u0 * 3; // eslint-disable-line camelcase
  199. var t0t0u0_3 = t0 * t0 * u0 * 3; // eslint-disable-line camelcase
  200. var t0t0t0 = t0 * t0 * t0;
  201. //
  202. var u0u0u1 = u0 * u0 * u1;
  203. var t0u0u1_3 = t0 * u0 * u1 + u0 * t0 * u1 + u0 * u0 * t1; // eslint-disable-line camelcase
  204. var t0t0u1_3 = t0 * t0 * u1 + u0 * t0 * t1 + t0 * u0 * t1; // eslint-disable-line camelcase
  205. var t0t0t1 = t0 * t0 * t1;
  206. //
  207. var u0u1u1 = u0 * u1 * u1;
  208. var t0u1u1_3 = t0 * u1 * u1 + u0 * t1 * u1 + u0 * u1 * t1; // eslint-disable-line camelcase
  209. var t0t1u1_3 = t0 * t1 * u1 + u0 * t1 * t1 + t0 * u1 * t1; // eslint-disable-line camelcase
  210. var t0t1t1 = t0 * t1 * t1;
  211. //
  212. var u1u1u1 = u1 * u1 * u1;
  213. var t1u1u1_3 = t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1; // eslint-disable-line camelcase
  214. var t1t1u1_3 = t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1; // eslint-disable-line camelcase
  215. var t1t1t1 = t1 * t1 * t1;
  216. for (i = 0; i < len; i += 1) {
  217. bezierSegmentPoints[i * 4] = math.round((u0u0u0 * pt1[i] + t0u0u0_3 * pt3[i] + t0t0u0_3 * pt4[i] + t0t0t0 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase
  218. bezierSegmentPoints[i * 4 + 1] = math.round((u0u0u1 * pt1[i] + t0u0u1_3 * pt3[i] + t0t0u1_3 * pt4[i] + t0t0t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase
  219. bezierSegmentPoints[i * 4 + 2] = math.round((u0u1u1 * pt1[i] + t0u1u1_3 * pt3[i] + t0t1u1_3 * pt4[i] + t0t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase
  220. bezierSegmentPoints[i * 4 + 3] = math.round((u1u1u1 * pt1[i] + t1u1u1_3 * pt3[i] + t1t1u1_3 * pt4[i] + t1t1t1 * pt2[i]) * 1000) / 1000; // eslint-disable-line camelcase
  221. }
  222. return bezierSegmentPoints;
  223. }
  224. return {
  225. getSegmentsLength: getSegmentsLength,
  226. getNewSegment: getNewSegment,
  227. getPointInSegment: getPointInSegment,
  228. buildBezierData: buildBezierData,
  229. pointOnLine2D: pointOnLine2D,
  230. pointOnLine3D: pointOnLine3D,
  231. };
  232. }
  233. const bez = bezFunction();
  234. export default bez;