fit_curve.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * JavaScript code in this page
  4. *
  5. * Copyright 2022 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * JavaScript code in this page
  21. */
  22. /******/ var __webpack_modules__ = ([
  23. /* 0 */,
  24. /* 1 */
  25. /***/ ((module) => {
  26. function fitCurve(points, maxError, progressCallback) {
  27. if (!Array.isArray(points)) {
  28. throw new TypeError("First argument should be an array");
  29. }
  30. points.forEach(point => {
  31. if (!Array.isArray(point) || point.some(item => typeof item !== 'number') || point.length !== points[0].length) {
  32. throw Error("Each point should be an array of numbers. Each point should have the same amount of numbers.");
  33. }
  34. });
  35. points = points.filter((point, i) => i === 0 || !point.every((val, j) => val === points[i - 1][j]));
  36. if (points.length < 2) {
  37. return [];
  38. }
  39. const len = points.length;
  40. const leftTangent = createTangent(points[1], points[0]);
  41. const rightTangent = createTangent(points[len - 2], points[len - 1]);
  42. return fitCubic(points, leftTangent, rightTangent, maxError, progressCallback);
  43. }
  44. function fitCubic(points, leftTangent, rightTangent, error, progressCallback) {
  45. const MaxIterations = 20;
  46. var bezCurve, u, uPrime, maxError, prevErr, splitPoint, prevSplit, centerVector, toCenterTangent, fromCenterTangent, beziers, dist, i;
  47. if (points.length === 2) {
  48. dist = maths.vectorLen(maths.subtract(points[0], points[1])) / 3.0;
  49. bezCurve = [points[0], maths.addArrays(points[0], maths.mulItems(leftTangent, dist)), maths.addArrays(points[1], maths.mulItems(rightTangent, dist)), points[1]];
  50. return [bezCurve];
  51. }
  52. u = chordLengthParameterize(points);
  53. [bezCurve, maxError, splitPoint] = generateAndReport(points, u, u, leftTangent, rightTangent, progressCallback);
  54. if (maxError === 0 || maxError < error) {
  55. return [bezCurve];
  56. }
  57. if (maxError < error * error) {
  58. uPrime = u;
  59. prevErr = maxError;
  60. prevSplit = splitPoint;
  61. for (i = 0; i < MaxIterations; i++) {
  62. uPrime = reparameterize(bezCurve, points, uPrime);
  63. [bezCurve, maxError, splitPoint] = generateAndReport(points, u, uPrime, leftTangent, rightTangent, progressCallback);
  64. if (maxError < error) {
  65. return [bezCurve];
  66. } else if (splitPoint === prevSplit) {
  67. let errChange = maxError / prevErr;
  68. if (errChange > .9999 && errChange < 1.0001) {
  69. break;
  70. }
  71. }
  72. prevErr = maxError;
  73. prevSplit = splitPoint;
  74. }
  75. }
  76. beziers = [];
  77. centerVector = maths.subtract(points[splitPoint - 1], points[splitPoint + 1]);
  78. if (centerVector.every(val => val === 0)) {
  79. centerVector = maths.subtract(points[splitPoint - 1], points[splitPoint]);
  80. [centerVector[0], centerVector[1]] = [-centerVector[1], centerVector[0]];
  81. }
  82. toCenterTangent = maths.normalize(centerVector);
  83. fromCenterTangent = maths.mulItems(toCenterTangent, -1);
  84. beziers = beziers.concat(fitCubic(points.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error, progressCallback));
  85. beziers = beziers.concat(fitCubic(points.slice(splitPoint), fromCenterTangent, rightTangent, error, progressCallback));
  86. return beziers;
  87. }
  88. ;
  89. function generateAndReport(points, paramsOrig, paramsPrime, leftTangent, rightTangent, progressCallback) {
  90. var bezCurve, maxError, splitPoint;
  91. bezCurve = generateBezier(points, paramsPrime, leftTangent, rightTangent, progressCallback);
  92. [maxError, splitPoint] = computeMaxError(points, bezCurve, paramsOrig);
  93. if (progressCallback) {
  94. progressCallback({
  95. bez: bezCurve,
  96. points: points,
  97. params: paramsOrig,
  98. maxErr: maxError,
  99. maxPoint: splitPoint
  100. });
  101. }
  102. return [bezCurve, maxError, splitPoint];
  103. }
  104. function generateBezier(points, parameters, leftTangent, rightTangent) {
  105. var bezCurve,
  106. A,
  107. a,
  108. C,
  109. X,
  110. det_C0_C1,
  111. det_C0_X,
  112. det_X_C1,
  113. alpha_l,
  114. alpha_r,
  115. epsilon,
  116. segLength,
  117. i,
  118. len,
  119. tmp,
  120. u,
  121. ux,
  122. firstPoint = points[0],
  123. lastPoint = points[points.length - 1];
  124. bezCurve = [firstPoint, null, null, lastPoint];
  125. A = maths.zeros_Xx2x2(parameters.length);
  126. for (i = 0, len = parameters.length; i < len; i++) {
  127. u = parameters[i];
  128. ux = 1 - u;
  129. a = A[i];
  130. a[0] = maths.mulItems(leftTangent, 3 * u * (ux * ux));
  131. a[1] = maths.mulItems(rightTangent, 3 * ux * (u * u));
  132. }
  133. C = [[0, 0], [0, 0]];
  134. X = [0, 0];
  135. for (i = 0, len = points.length; i < len; i++) {
  136. u = parameters[i];
  137. a = A[i];
  138. C[0][0] += maths.dot(a[0], a[0]);
  139. C[0][1] += maths.dot(a[0], a[1]);
  140. C[1][0] += maths.dot(a[0], a[1]);
  141. C[1][1] += maths.dot(a[1], a[1]);
  142. tmp = maths.subtract(points[i], bezier.q([firstPoint, firstPoint, lastPoint, lastPoint], u));
  143. X[0] += maths.dot(a[0], tmp);
  144. X[1] += maths.dot(a[1], tmp);
  145. }
  146. det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
  147. det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
  148. det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
  149. alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1;
  150. alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1;
  151. segLength = maths.vectorLen(maths.subtract(firstPoint, lastPoint));
  152. epsilon = 1.0e-6 * segLength;
  153. if (alpha_l < epsilon || alpha_r < epsilon) {
  154. bezCurve[1] = maths.addArrays(firstPoint, maths.mulItems(leftTangent, segLength / 3.0));
  155. bezCurve[2] = maths.addArrays(lastPoint, maths.mulItems(rightTangent, segLength / 3.0));
  156. } else {
  157. bezCurve[1] = maths.addArrays(firstPoint, maths.mulItems(leftTangent, alpha_l));
  158. bezCurve[2] = maths.addArrays(lastPoint, maths.mulItems(rightTangent, alpha_r));
  159. }
  160. return bezCurve;
  161. }
  162. ;
  163. function reparameterize(bezier, points, parameters) {
  164. return parameters.map((p, i) => newtonRaphsonRootFind(bezier, points[i], p));
  165. }
  166. ;
  167. function newtonRaphsonRootFind(bez, point, u) {
  168. var d = maths.subtract(bezier.q(bez, u), point),
  169. qprime = bezier.qprime(bez, u),
  170. numerator = maths.mulMatrix(d, qprime),
  171. denominator = maths.sum(maths.squareItems(qprime)) + 2 * maths.mulMatrix(d, bezier.qprimeprime(bez, u));
  172. if (denominator === 0) {
  173. return u;
  174. } else {
  175. return u - numerator / denominator;
  176. }
  177. }
  178. ;
  179. function chordLengthParameterize(points) {
  180. var u = [],
  181. currU,
  182. prevU,
  183. prevP;
  184. points.forEach((p, i) => {
  185. currU = i ? prevU + maths.vectorLen(maths.subtract(p, prevP)) : 0;
  186. u.push(currU);
  187. prevU = currU;
  188. prevP = p;
  189. });
  190. u = u.map(x => x / prevU);
  191. return u;
  192. }
  193. ;
  194. function computeMaxError(points, bez, parameters) {
  195. var dist, maxDist, splitPoint, v, i, count, point, t;
  196. maxDist = 0;
  197. splitPoint = Math.floor(points.length / 2);
  198. const t_distMap = mapTtoRelativeDistances(bez, 10);
  199. for (i = 0, count = points.length; i < count; i++) {
  200. point = points[i];
  201. t = find_t(bez, parameters[i], t_distMap, 10);
  202. v = maths.subtract(bezier.q(bez, t), point);
  203. dist = v[0] * v[0] + v[1] * v[1];
  204. if (dist > maxDist) {
  205. maxDist = dist;
  206. splitPoint = i;
  207. }
  208. }
  209. return [maxDist, splitPoint];
  210. }
  211. ;
  212. var mapTtoRelativeDistances = function (bez, B_parts) {
  213. var B_t_curr;
  214. var B_t_dist = [0];
  215. var B_t_prev = bez[0];
  216. var sumLen = 0;
  217. for (var i = 1; i <= B_parts; i++) {
  218. B_t_curr = bezier.q(bez, i / B_parts);
  219. sumLen += maths.vectorLen(maths.subtract(B_t_curr, B_t_prev));
  220. B_t_dist.push(sumLen);
  221. B_t_prev = B_t_curr;
  222. }
  223. B_t_dist = B_t_dist.map(x => x / sumLen);
  224. return B_t_dist;
  225. };
  226. function find_t(bez, param, t_distMap, B_parts) {
  227. if (param < 0) {
  228. return 0;
  229. }
  230. if (param > 1) {
  231. return 1;
  232. }
  233. var lenMax, lenMin, tMax, tMin, t;
  234. for (var i = 1; i <= B_parts; i++) {
  235. if (param <= t_distMap[i]) {
  236. tMin = (i - 1) / B_parts;
  237. tMax = i / B_parts;
  238. lenMin = t_distMap[i - 1];
  239. lenMax = t_distMap[i];
  240. t = (param - lenMin) / (lenMax - lenMin) * (tMax - tMin) + tMin;
  241. break;
  242. }
  243. }
  244. return t;
  245. }
  246. function createTangent(pointA, pointB) {
  247. return maths.normalize(maths.subtract(pointA, pointB));
  248. }
  249. class maths {
  250. static zeros_Xx2x2(x) {
  251. var zs = [];
  252. while (x--) {
  253. zs.push([0, 0]);
  254. }
  255. return zs;
  256. }
  257. static mulItems(items, multiplier) {
  258. return items.map(x => x * multiplier);
  259. }
  260. static mulMatrix(m1, m2) {
  261. return m1.reduce((sum, x1, i) => sum + x1 * m2[i], 0);
  262. }
  263. static subtract(arr1, arr2) {
  264. return arr1.map((x1, i) => x1 - arr2[i]);
  265. }
  266. static addArrays(arr1, arr2) {
  267. return arr1.map((x1, i) => x1 + arr2[i]);
  268. }
  269. static addItems(items, addition) {
  270. return items.map(x => x + addition);
  271. }
  272. static sum(items) {
  273. return items.reduce((sum, x) => sum + x);
  274. }
  275. static dot(m1, m2) {
  276. return maths.mulMatrix(m1, m2);
  277. }
  278. static vectorLen(v) {
  279. return Math.hypot(...v);
  280. }
  281. static divItems(items, divisor) {
  282. return items.map(x => x / divisor);
  283. }
  284. static squareItems(items) {
  285. return items.map(x => x * x);
  286. }
  287. static normalize(v) {
  288. return this.divItems(v, this.vectorLen(v));
  289. }
  290. }
  291. class bezier {
  292. static q(ctrlPoly, t) {
  293. var tx = 1.0 - t;
  294. var pA = maths.mulItems(ctrlPoly[0], tx * tx * tx),
  295. pB = maths.mulItems(ctrlPoly[1], 3 * tx * tx * t),
  296. pC = maths.mulItems(ctrlPoly[2], 3 * tx * t * t),
  297. pD = maths.mulItems(ctrlPoly[3], t * t * t);
  298. return maths.addArrays(maths.addArrays(pA, pB), maths.addArrays(pC, pD));
  299. }
  300. static qprime(ctrlPoly, t) {
  301. var tx = 1.0 - t;
  302. var pA = maths.mulItems(maths.subtract(ctrlPoly[1], ctrlPoly[0]), 3 * tx * tx),
  303. pB = maths.mulItems(maths.subtract(ctrlPoly[2], ctrlPoly[1]), 6 * tx * t),
  304. pC = maths.mulItems(maths.subtract(ctrlPoly[3], ctrlPoly[2]), 3 * t * t);
  305. return maths.addArrays(maths.addArrays(pA, pB), pC);
  306. }
  307. static qprimeprime(ctrlPoly, t) {
  308. return maths.addArrays(maths.mulItems(maths.addArrays(maths.subtract(ctrlPoly[2], maths.mulItems(ctrlPoly[1], 2)), ctrlPoly[0]), 6 * (1.0 - t)), maths.mulItems(maths.addArrays(maths.subtract(ctrlPoly[3], maths.mulItems(ctrlPoly[2], 2)), ctrlPoly[1]), 6 * t));
  309. }
  310. }
  311. module.exports = fitCurve;
  312. module.exports.fitCubic = fitCubic;
  313. module.exports.createTangent = createTangent;
  314. /***/ })
  315. /******/ ]);
  316. /************************************************************************/
  317. /******/ // The module cache
  318. /******/ var __webpack_module_cache__ = {};
  319. /******/
  320. /******/ // The require function
  321. /******/ function __webpack_require__(moduleId) {
  322. /******/ // Check if module is in cache
  323. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  324. /******/ if (cachedModule !== undefined) {
  325. /******/ return cachedModule.exports;
  326. /******/ }
  327. /******/ // Create a new module (and put it into the cache)
  328. /******/ var module = __webpack_module_cache__[moduleId] = {
  329. /******/ // no module.id needed
  330. /******/ // no module.loaded needed
  331. /******/ exports: {}
  332. /******/ };
  333. /******/
  334. /******/ // Execute the module function
  335. /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  336. /******/
  337. /******/ // Return the exports of the module
  338. /******/ return module.exports;
  339. /******/ }
  340. /******/
  341. /************************************************************************/
  342. var __webpack_exports__ = {};
  343. // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
  344. (() => {
  345. var exports = __webpack_exports__;
  346. Object.defineProperty(exports, "__esModule", ({
  347. value: true
  348. }));
  349. exports.fitCurve = void 0;
  350. const fitCurve = __webpack_require__(1);
  351. exports.fitCurve = fitCurve;
  352. })();
  353. var __webpack_exports___esModule = __webpack_exports__.__esModule;
  354. var __webpack_exports__fitCurve = __webpack_exports__.fitCurve;
  355. export { __webpack_exports___esModule as __esModule, __webpack_exports__fitCurve as fitCurve };