path.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. var PathProxy = require("../core/PathProxy");
  2. var line = require("./line");
  3. var cubic = require("./cubic");
  4. var quadratic = require("./quadratic");
  5. var arc = require("./arc");
  6. var _util = require("./util");
  7. var normalizeRadian = _util.normalizeRadian;
  8. var curve = require("../core/curve");
  9. var windingLine = require("./windingLine");
  10. var CMD = PathProxy.CMD;
  11. var PI2 = Math.PI * 2;
  12. var EPSILON = 1e-4;
  13. function isAroundEqual(a, b) {
  14. return Math.abs(a - b) < EPSILON;
  15. } // 临时数组
  16. var roots = [-1, -1, -1];
  17. var extrema = [-1, -1];
  18. function swapExtrema() {
  19. var tmp = extrema[0];
  20. extrema[0] = extrema[1];
  21. extrema[1] = tmp;
  22. }
  23. function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
  24. // Quick reject
  25. if (y > y0 && y > y1 && y > y2 && y > y3 || y < y0 && y < y1 && y < y2 && y < y3) {
  26. return 0;
  27. }
  28. var nRoots = curve.cubicRootAt(y0, y1, y2, y3, y, roots);
  29. if (nRoots === 0) {
  30. return 0;
  31. } else {
  32. var w = 0;
  33. var nExtrema = -1;
  34. var y0_;
  35. var y1_;
  36. for (var i = 0; i < nRoots; i++) {
  37. var t = roots[i]; // Avoid winding error when intersection point is the connect point of two line of polygon
  38. var unit = t === 0 || t === 1 ? 0.5 : 1;
  39. var x_ = curve.cubicAt(x0, x1, x2, x3, t);
  40. if (x_ < x) {
  41. // Quick reject
  42. continue;
  43. }
  44. if (nExtrema < 0) {
  45. nExtrema = curve.cubicExtrema(y0, y1, y2, y3, extrema);
  46. if (extrema[1] < extrema[0] && nExtrema > 1) {
  47. swapExtrema();
  48. }
  49. y0_ = curve.cubicAt(y0, y1, y2, y3, extrema[0]);
  50. if (nExtrema > 1) {
  51. y1_ = curve.cubicAt(y0, y1, y2, y3, extrema[1]);
  52. }
  53. }
  54. if (nExtrema === 2) {
  55. // 分成三段单调函数
  56. if (t < extrema[0]) {
  57. w += y0_ < y0 ? unit : -unit;
  58. } else if (t < extrema[1]) {
  59. w += y1_ < y0_ ? unit : -unit;
  60. } else {
  61. w += y3 < y1_ ? unit : -unit;
  62. }
  63. } else {
  64. // 分成两段单调函数
  65. if (t < extrema[0]) {
  66. w += y0_ < y0 ? unit : -unit;
  67. } else {
  68. w += y3 < y0_ ? unit : -unit;
  69. }
  70. }
  71. }
  72. return w;
  73. }
  74. }
  75. function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
  76. // Quick reject
  77. if (y > y0 && y > y1 && y > y2 || y < y0 && y < y1 && y < y2) {
  78. return 0;
  79. }
  80. var nRoots = curve.quadraticRootAt(y0, y1, y2, y, roots);
  81. if (nRoots === 0) {
  82. return 0;
  83. } else {
  84. var t = curve.quadraticExtremum(y0, y1, y2);
  85. if (t >= 0 && t <= 1) {
  86. var w = 0;
  87. var y_ = curve.quadraticAt(y0, y1, y2, t);
  88. for (var i = 0; i < nRoots; i++) {
  89. // Remove one endpoint.
  90. var unit = roots[i] === 0 || roots[i] === 1 ? 0.5 : 1;
  91. var x_ = curve.quadraticAt(x0, x1, x2, roots[i]);
  92. if (x_ < x) {
  93. // Quick reject
  94. continue;
  95. }
  96. if (roots[i] < t) {
  97. w += y_ < y0 ? unit : -unit;
  98. } else {
  99. w += y2 < y_ ? unit : -unit;
  100. }
  101. }
  102. return w;
  103. } else {
  104. // Remove one endpoint.
  105. var unit = roots[0] === 0 || roots[0] === 1 ? 0.5 : 1;
  106. var x_ = curve.quadraticAt(x0, x1, x2, roots[0]);
  107. if (x_ < x) {
  108. // Quick reject
  109. return 0;
  110. }
  111. return y2 < y0 ? unit : -unit;
  112. }
  113. }
  114. } // TODO
  115. // Arc 旋转
  116. function windingArc(cx, cy, r, startAngle, endAngle, anticlockwise, x, y) {
  117. y -= cy;
  118. if (y > r || y < -r) {
  119. return 0;
  120. }
  121. var tmp = Math.sqrt(r * r - y * y);
  122. roots[0] = -tmp;
  123. roots[1] = tmp;
  124. var diff = Math.abs(startAngle - endAngle);
  125. if (diff < 1e-4) {
  126. return 0;
  127. }
  128. if (diff % PI2 < 1e-4) {
  129. // Is a circle
  130. startAngle = 0;
  131. endAngle = PI2;
  132. var dir = anticlockwise ? 1 : -1;
  133. if (x >= roots[0] + cx && x <= roots[1] + cx) {
  134. return dir;
  135. } else {
  136. return 0;
  137. }
  138. }
  139. if (anticlockwise) {
  140. var tmp = startAngle;
  141. startAngle = normalizeRadian(endAngle);
  142. endAngle = normalizeRadian(tmp);
  143. } else {
  144. startAngle = normalizeRadian(startAngle);
  145. endAngle = normalizeRadian(endAngle);
  146. }
  147. if (startAngle > endAngle) {
  148. endAngle += PI2;
  149. }
  150. var w = 0;
  151. for (var i = 0; i < 2; i++) {
  152. var x_ = roots[i];
  153. if (x_ + cx > x) {
  154. var angle = Math.atan2(y, x_);
  155. var dir = anticlockwise ? 1 : -1;
  156. if (angle < 0) {
  157. angle = PI2 + angle;
  158. }
  159. if (angle >= startAngle && angle <= endAngle || angle + PI2 >= startAngle && angle + PI2 <= endAngle) {
  160. if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
  161. dir = -dir;
  162. }
  163. w += dir;
  164. }
  165. }
  166. }
  167. return w;
  168. }
  169. function containPath(data, lineWidth, isStroke, x, y) {
  170. var w = 0;
  171. var xi = 0;
  172. var yi = 0;
  173. var x0 = 0;
  174. var y0 = 0;
  175. for (var i = 0; i < data.length;) {
  176. var cmd = data[i++]; // Begin a new subpath
  177. if (cmd === CMD.M && i > 1) {
  178. // Close previous subpath
  179. if (!isStroke) {
  180. w += windingLine(xi, yi, x0, y0, x, y);
  181. } // 如果被任何一个 subpath 包含
  182. // if (w !== 0) {
  183. // return true;
  184. // }
  185. }
  186. if (i === 1) {
  187. // 如果第一个命令是 L, C, Q
  188. // 则 previous point 同绘制命令的第一个 point
  189. //
  190. // 第一个命令为 Arc 的情况下会在后面特殊处理
  191. xi = data[i];
  192. yi = data[i + 1];
  193. x0 = xi;
  194. y0 = yi;
  195. }
  196. switch (cmd) {
  197. case CMD.M:
  198. // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
  199. // 在 closePath 的时候使用
  200. x0 = data[i++];
  201. y0 = data[i++];
  202. xi = x0;
  203. yi = y0;
  204. break;
  205. case CMD.L:
  206. if (isStroke) {
  207. if (line.containStroke(xi, yi, data[i], data[i + 1], lineWidth, x, y)) {
  208. return true;
  209. }
  210. } else {
  211. // NOTE 在第一个命令为 L, C, Q 的时候会计算出 NaN
  212. w += windingLine(xi, yi, data[i], data[i + 1], x, y) || 0;
  213. }
  214. xi = data[i++];
  215. yi = data[i++];
  216. break;
  217. case CMD.C:
  218. if (isStroke) {
  219. if (cubic.containStroke(xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], lineWidth, x, y)) {
  220. return true;
  221. }
  222. } else {
  223. w += windingCubic(xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], x, y) || 0;
  224. }
  225. xi = data[i++];
  226. yi = data[i++];
  227. break;
  228. case CMD.Q:
  229. if (isStroke) {
  230. if (quadratic.containStroke(xi, yi, data[i++], data[i++], data[i], data[i + 1], lineWidth, x, y)) {
  231. return true;
  232. }
  233. } else {
  234. w += windingQuadratic(xi, yi, data[i++], data[i++], data[i], data[i + 1], x, y) || 0;
  235. }
  236. xi = data[i++];
  237. yi = data[i++];
  238. break;
  239. case CMD.A:
  240. // TODO Arc 判断的开销比较大
  241. var cx = data[i++];
  242. var cy = data[i++];
  243. var rx = data[i++];
  244. var ry = data[i++];
  245. var theta = data[i++];
  246. var dTheta = data[i++]; // TODO Arc 旋转
  247. i += 1;
  248. var anticlockwise = 1 - data[i++];
  249. var x1 = Math.cos(theta) * rx + cx;
  250. var y1 = Math.sin(theta) * ry + cy; // 不是直接使用 arc 命令
  251. if (i > 1) {
  252. w += windingLine(xi, yi, x1, y1, x, y);
  253. } else {
  254. // 第一个命令起点还未定义
  255. x0 = x1;
  256. y0 = y1;
  257. } // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
  258. var _x = (x - cx) * ry / rx + cx;
  259. if (isStroke) {
  260. if (arc.containStroke(cx, cy, ry, theta, theta + dTheta, anticlockwise, lineWidth, _x, y)) {
  261. return true;
  262. }
  263. } else {
  264. w += windingArc(cx, cy, ry, theta, theta + dTheta, anticlockwise, _x, y);
  265. }
  266. xi = Math.cos(theta + dTheta) * rx + cx;
  267. yi = Math.sin(theta + dTheta) * ry + cy;
  268. break;
  269. case CMD.R:
  270. x0 = xi = data[i++];
  271. y0 = yi = data[i++];
  272. var width = data[i++];
  273. var height = data[i++];
  274. var x1 = x0 + width;
  275. var y1 = y0 + height;
  276. if (isStroke) {
  277. if (line.containStroke(x0, y0, x1, y0, lineWidth, x, y) || line.containStroke(x1, y0, x1, y1, lineWidth, x, y) || line.containStroke(x1, y1, x0, y1, lineWidth, x, y) || line.containStroke(x0, y1, x0, y0, lineWidth, x, y)) {
  278. return true;
  279. }
  280. } else {
  281. // FIXME Clockwise ?
  282. w += windingLine(x1, y0, x1, y1, x, y);
  283. w += windingLine(x0, y1, x0, y0, x, y);
  284. }
  285. break;
  286. case CMD.Z:
  287. if (isStroke) {
  288. if (line.containStroke(xi, yi, x0, y0, lineWidth, x, y)) {
  289. return true;
  290. }
  291. } else {
  292. // Close a subpath
  293. w += windingLine(xi, yi, x0, y0, x, y); // 如果被任何一个 subpath 包含
  294. // FIXME subpaths may overlap
  295. // if (w !== 0) {
  296. // return true;
  297. // }
  298. }
  299. xi = x0;
  300. yi = y0;
  301. break;
  302. }
  303. }
  304. if (!isStroke && !isAroundEqual(yi, y0)) {
  305. w += windingLine(xi, yi, x0, y0, x, y) || 0;
  306. }
  307. return w !== 0;
  308. }
  309. function contain(pathData, x, y) {
  310. return containPath(pathData, 0, false, x, y);
  311. }
  312. function containStroke(pathData, lineWidth, x, y) {
  313. return containPath(pathData, lineWidth, true, x, y);
  314. }
  315. exports.contain = contain;
  316. exports.containStroke = containStroke;