OffsetPathModifier.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import {
  2. roundCorner,
  3. } from '../common';
  4. import {
  5. extendPrototype,
  6. } from '../functionExtensions';
  7. import PropertyFactory from '../PropertyFactory';
  8. import shapePool from '../pooling/shape_pool';
  9. import {
  10. ShapeModifier,
  11. } from './ShapeModifiers';
  12. import {
  13. PolynomialBezier,
  14. polarOffset,
  15. lineIntersection,
  16. pointDistance,
  17. pointEqual,
  18. floatEqual,
  19. } from '../PolynomialBezier';
  20. function linearOffset(p1, p2, amount) {
  21. var angle = Math.atan2(p2[0] - p1[0], p2[1] - p1[1]);
  22. return [
  23. polarOffset(p1, angle, amount),
  24. polarOffset(p2, angle, amount),
  25. ];
  26. }
  27. function offsetSegment(segment, amount) {
  28. var p0; var p1a; var p1b; var p2b; var p2a; var
  29. p3;
  30. var e;
  31. e = linearOffset(segment.points[0], segment.points[1], amount);
  32. p0 = e[0];
  33. p1a = e[1];
  34. e = linearOffset(segment.points[1], segment.points[2], amount);
  35. p1b = e[0];
  36. p2b = e[1];
  37. e = linearOffset(segment.points[2], segment.points[3], amount);
  38. p2a = e[0];
  39. p3 = e[1];
  40. var p1 = lineIntersection(p0, p1a, p1b, p2b);
  41. if (p1 === null) p1 = p1a;
  42. var p2 = lineIntersection(p2a, p3, p1b, p2b);
  43. if (p2 === null) p2 = p2a;
  44. return new PolynomialBezier(p0, p1, p2, p3);
  45. }
  46. function joinLines(outputBezier, seg1, seg2, lineJoin, miterLimit) {
  47. var p0 = seg1.points[3];
  48. var p1 = seg2.points[0];
  49. // Bevel
  50. if (lineJoin === 3) return p0;
  51. // Connected, they don't need a joint
  52. if (pointEqual(p0, p1)) return p0;
  53. // Round
  54. if (lineJoin === 2) {
  55. var angleOut = -seg1.tangentAngle(1);
  56. var angleIn = -seg2.tangentAngle(0) + Math.PI;
  57. var center = lineIntersection(
  58. p0,
  59. polarOffset(p0, angleOut + Math.PI / 2, 100),
  60. p1,
  61. polarOffset(p1, angleOut + Math.PI / 2, 100)
  62. );
  63. var radius = center ? pointDistance(center, p0) : pointDistance(p0, p1) / 2;
  64. var tan = polarOffset(p0, angleOut, 2 * radius * roundCorner);
  65. outputBezier.setXYAt(tan[0], tan[1], 'o', outputBezier.length() - 1);
  66. tan = polarOffset(p1, angleIn, 2 * radius * roundCorner);
  67. outputBezier.setTripleAt(p1[0], p1[1], p1[0], p1[1], tan[0], tan[1], outputBezier.length());
  68. return p1;
  69. }
  70. // Miter
  71. var t0 = pointEqual(p0, seg1.points[2]) ? seg1.points[0] : seg1.points[2];
  72. var t1 = pointEqual(p1, seg2.points[1]) ? seg2.points[3] : seg2.points[1];
  73. var intersection = lineIntersection(t0, p0, p1, t1);
  74. if (intersection && pointDistance(intersection, p0) < miterLimit) {
  75. outputBezier.setTripleAt(
  76. intersection[0],
  77. intersection[1],
  78. intersection[0],
  79. intersection[1],
  80. intersection[0],
  81. intersection[1],
  82. outputBezier.length()
  83. );
  84. return intersection;
  85. }
  86. return p0;
  87. }
  88. function getIntersection(a, b) {
  89. const intersect = a.intersections(b);
  90. if (intersect.length && floatEqual(intersect[0][0], 1)) intersect.shift();
  91. if (intersect.length) return intersect[0];
  92. return null;
  93. }
  94. function pruneSegmentIntersection(a, b) {
  95. var outa = a.slice();
  96. var outb = b.slice();
  97. var intersect = getIntersection(a[a.length - 1], b[0]);
  98. if (intersect) {
  99. outa[a.length - 1] = a[a.length - 1].split(intersect[0])[0];
  100. outb[0] = b[0].split(intersect[1])[1];
  101. }
  102. if (a.length > 1 && b.length > 1) {
  103. intersect = getIntersection(a[0], b[b.length - 1]);
  104. if (intersect) {
  105. return [
  106. [a[0].split(intersect[0])[0]],
  107. [b[b.length - 1].split(intersect[1])[1]],
  108. ];
  109. }
  110. }
  111. return [outa, outb];
  112. }
  113. function pruneIntersections(segments) {
  114. var e;
  115. for (var i = 1; i < segments.length; i += 1) {
  116. e = pruneSegmentIntersection(segments[i - 1], segments[i]);
  117. segments[i - 1] = e[0];
  118. segments[i] = e[1];
  119. }
  120. if (segments.length > 1) {
  121. e = pruneSegmentIntersection(segments[segments.length - 1], segments[0]);
  122. segments[segments.length - 1] = e[0];
  123. segments[0] = e[1];
  124. }
  125. return segments;
  126. }
  127. function offsetSegmentSplit(segment, amount) {
  128. /*
  129. We split each bezier segment into smaller pieces based
  130. on inflection points, this ensures the control point
  131. polygon is convex.
  132. (A cubic bezier can have none, one, or two inflection points)
  133. */
  134. var flex = segment.inflectionPoints();
  135. var left;
  136. var right;
  137. var split;
  138. var mid;
  139. if (flex.length === 0) {
  140. return [offsetSegment(segment, amount)];
  141. }
  142. if (flex.length === 1 || floatEqual(flex[1], 1)) {
  143. split = segment.split(flex[0]);
  144. left = split[0];
  145. right = split[1];
  146. return [
  147. offsetSegment(left, amount),
  148. offsetSegment(right, amount),
  149. ];
  150. }
  151. split = segment.split(flex[0]);
  152. left = split[0];
  153. var t = (flex[1] - flex[0]) / (1 - flex[0]);
  154. split = split[1].split(t);
  155. mid = split[0];
  156. right = split[1];
  157. return [
  158. offsetSegment(left, amount),
  159. offsetSegment(mid, amount),
  160. offsetSegment(right, amount),
  161. ];
  162. }
  163. function OffsetPathModifier() {}
  164. extendPrototype([ShapeModifier], OffsetPathModifier);
  165. OffsetPathModifier.prototype.initModifierProperties = function (elem, data) {
  166. this.getValue = this.processKeys;
  167. this.amount = PropertyFactory.getProp(elem, data.a, 0, null, this);
  168. this.miterLimit = PropertyFactory.getProp(elem, data.ml, 0, null, this);
  169. this.lineJoin = data.lj;
  170. this._isAnimated = this.amount.effectsSequence.length !== 0;
  171. };
  172. OffsetPathModifier.prototype.processPath = function (inputBezier, amount, lineJoin, miterLimit) {
  173. var outputBezier = shapePool.newElement();
  174. outputBezier.c = inputBezier.c;
  175. var count = inputBezier.length();
  176. if (!inputBezier.c) {
  177. count -= 1;
  178. }
  179. var i; var j; var segment;
  180. var multiSegments = [];
  181. for (i = 0; i < count; i += 1) {
  182. segment = PolynomialBezier.shapeSegment(inputBezier, i);
  183. multiSegments.push(offsetSegmentSplit(segment, amount));
  184. }
  185. if (!inputBezier.c) {
  186. for (i = count - 1; i >= 0; i -= 1) {
  187. segment = PolynomialBezier.shapeSegmentInverted(inputBezier, i);
  188. multiSegments.push(offsetSegmentSplit(segment, amount));
  189. }
  190. }
  191. multiSegments = pruneIntersections(multiSegments);
  192. // Add bezier segments to the output and apply line joints
  193. var lastPoint = null;
  194. var lastSeg = null;
  195. for (i = 0; i < multiSegments.length; i += 1) {
  196. var multiSegment = multiSegments[i];
  197. if (lastSeg) lastPoint = joinLines(outputBezier, lastSeg, multiSegment[0], lineJoin, miterLimit);
  198. lastSeg = multiSegment[multiSegment.length - 1];
  199. for (j = 0; j < multiSegment.length; j += 1) {
  200. segment = multiSegment[j];
  201. if (lastPoint && pointEqual(segment.points[0], lastPoint)) {
  202. outputBezier.setXYAt(segment.points[1][0], segment.points[1][1], 'o', outputBezier.length() - 1);
  203. } else {
  204. outputBezier.setTripleAt(
  205. segment.points[0][0],
  206. segment.points[0][1],
  207. segment.points[1][0],
  208. segment.points[1][1],
  209. segment.points[0][0],
  210. segment.points[0][1],
  211. outputBezier.length()
  212. );
  213. }
  214. outputBezier.setTripleAt(
  215. segment.points[3][0],
  216. segment.points[3][1],
  217. segment.points[3][0],
  218. segment.points[3][1],
  219. segment.points[2][0],
  220. segment.points[2][1],
  221. outputBezier.length()
  222. );
  223. lastPoint = segment.points[3];
  224. }
  225. }
  226. if (multiSegments.length) joinLines(outputBezier, lastSeg, multiSegments[0][0], lineJoin, miterLimit);
  227. return outputBezier;
  228. };
  229. OffsetPathModifier.prototype.processShapes = function (_isFirstFrame) {
  230. var shapePaths;
  231. var i;
  232. var len = this.shapes.length;
  233. var j;
  234. var jLen;
  235. var amount = this.amount.v;
  236. var miterLimit = this.miterLimit.v;
  237. var lineJoin = this.lineJoin;
  238. if (amount !== 0) {
  239. var shapeData;
  240. var localShapeCollection;
  241. for (i = 0; i < len; i += 1) {
  242. shapeData = this.shapes[i];
  243. localShapeCollection = shapeData.localShapeCollection;
  244. if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) {
  245. localShapeCollection.releaseShapes();
  246. shapeData.shape._mdf = true;
  247. shapePaths = shapeData.shape.paths.shapes;
  248. jLen = shapeData.shape.paths._length;
  249. for (j = 0; j < jLen; j += 1) {
  250. localShapeCollection.addShape(this.processPath(shapePaths[j], amount, lineJoin, miterLimit));
  251. }
  252. }
  253. shapeData.shape.paths = shapeData.localShapeCollection;
  254. }
  255. }
  256. if (!this.dynamicProperties.length) {
  257. this._mdf = false;
  258. }
  259. };
  260. export default OffsetPathModifier;