import { Point, findClosestClipPathTarget, isFillOrStrokeAffected, getOrCalculatePathTotalLength, Shape, AbstractRendererPlugin } from '@antv/g-lite'; import { vec3, mat4, vec2 } from 'gl-matrix'; import { arcToCubic, clamp } from '@antv/util'; function _regeneratorRuntime() { _regeneratorRuntime = function () { return exports; }; var exports = {}, Op = Object.prototype, hasOwn = Op.hasOwnProperty, defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }, $Symbol = "function" == typeof Symbol ? Symbol : {}, iteratorSymbol = $Symbol.iterator || "@@iterator", asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator", toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; function define(obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: !0, configurable: !0, writable: !0 }), obj[key]; } try { define({}, ""); } catch (err) { define = function (obj, key, value) { return obj[key] = value; }; } function wrap(innerFn, outerFn, self, tryLocsList) { var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator, generator = Object.create(protoGenerator.prototype), context = new Context(tryLocsList || []); return defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }), generator; } function tryCatch(fn, obj, arg) { try { return { type: "normal", arg: fn.call(obj, arg) }; } catch (err) { return { type: "throw", arg: err }; } } exports.wrap = wrap; var ContinueSentinel = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var IteratorPrototype = {}; define(IteratorPrototype, iteratorSymbol, function () { return this; }); var getProto = Object.getPrototypeOf, NativeIteratorPrototype = getProto && getProto(getProto(values([]))); NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol) && (IteratorPrototype = NativeIteratorPrototype); var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype); function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function (method) { define(prototype, method, function (arg) { return this._invoke(method, arg); }); }); } function AsyncIterator(generator, PromiseImpl) { function invoke(method, arg, resolve, reject) { var record = tryCatch(generator[method], generator, arg); if ("throw" !== record.type) { var result = record.arg, value = result.value; return value && "object" == typeof value && hasOwn.call(value, "__await") ? PromiseImpl.resolve(value.__await).then(function (value) { invoke("next", value, resolve, reject); }, function (err) { invoke("throw", err, resolve, reject); }) : PromiseImpl.resolve(value).then(function (unwrapped) { result.value = unwrapped, resolve(result); }, function (error) { return invoke("throw", error, resolve, reject); }); } reject(record.arg); } var previousPromise; defineProperty(this, "_invoke", { value: function (method, arg) { function callInvokeWithMethodAndArg() { return new PromiseImpl(function (resolve, reject) { invoke(method, arg, resolve, reject); }); } return previousPromise = previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(innerFn, self, context) { var state = "suspendedStart"; return function (method, arg) { if ("executing" === state) throw new Error("Generator is already running"); if ("completed" === state) { if ("throw" === method) throw arg; return doneResult(); } for (context.method = method, context.arg = arg;;) { var delegate = context.delegate; if (delegate) { var delegateResult = maybeInvokeDelegate(delegate, context); if (delegateResult) { if (delegateResult === ContinueSentinel) continue; return delegateResult; } } if ("next" === context.method) context.sent = context._sent = context.arg;else if ("throw" === context.method) { if ("suspendedStart" === state) throw state = "completed", context.arg; context.dispatchException(context.arg); } else "return" === context.method && context.abrupt("return", context.arg); state = "executing"; var record = tryCatch(innerFn, self, context); if ("normal" === record.type) { if (state = context.done ? "completed" : "suspendedYield", record.arg === ContinueSentinel) continue; return { value: record.arg, done: context.done }; } "throw" === record.type && (state = "completed", context.method = "throw", context.arg = record.arg); } }; } function maybeInvokeDelegate(delegate, context) { var methodName = context.method, method = delegate.iterator[methodName]; if (undefined === method) return context.delegate = null, "throw" === methodName && delegate.iterator.return && (context.method = "return", context.arg = undefined, maybeInvokeDelegate(delegate, context), "throw" === context.method) || "return" !== methodName && (context.method = "throw", context.arg = new TypeError("The iterator does not provide a '" + methodName + "' method")), ContinueSentinel; var record = tryCatch(method, delegate.iterator, context.arg); if ("throw" === record.type) return context.method = "throw", context.arg = record.arg, context.delegate = null, ContinueSentinel; var info = record.arg; return info ? info.done ? (context[delegate.resultName] = info.value, context.next = delegate.nextLoc, "return" !== context.method && (context.method = "next", context.arg = undefined), context.delegate = null, ContinueSentinel) : info : (context.method = "throw", context.arg = new TypeError("iterator result is not an object"), context.delegate = null, ContinueSentinel); } function pushTryEntry(locs) { var entry = { tryLoc: locs[0] }; 1 in locs && (entry.catchLoc = locs[1]), 2 in locs && (entry.finallyLoc = locs[2], entry.afterLoc = locs[3]), this.tryEntries.push(entry); } function resetTryEntry(entry) { var record = entry.completion || {}; record.type = "normal", delete record.arg, entry.completion = record; } function Context(tryLocsList) { this.tryEntries = [{ tryLoc: "root" }], tryLocsList.forEach(pushTryEntry, this), this.reset(!0); } function values(iterable) { if (iterable) { var iteratorMethod = iterable[iteratorSymbol]; if (iteratorMethod) return iteratorMethod.call(iterable); if ("function" == typeof iterable.next) return iterable; if (!isNaN(iterable.length)) { var i = -1, next = function next() { for (; ++i < iterable.length;) if (hasOwn.call(iterable, i)) return next.value = iterable[i], next.done = !1, next; return next.value = undefined, next.done = !0, next; }; return next.next = next; } } return { next: doneResult }; } function doneResult() { return { value: undefined, done: !0 }; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), defineProperty(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"), exports.isGeneratorFunction = function (genFun) { var ctor = "function" == typeof genFun && genFun.constructor; return !!ctor && (ctor === GeneratorFunction || "GeneratorFunction" === (ctor.displayName || ctor.name)); }, exports.mark = function (genFun) { return Object.setPrototypeOf ? Object.setPrototypeOf(genFun, GeneratorFunctionPrototype) : (genFun.__proto__ = GeneratorFunctionPrototype, define(genFun, toStringTagSymbol, "GeneratorFunction")), genFun.prototype = Object.create(Gp), genFun; }, exports.awrap = function (arg) { return { __await: arg }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, asyncIteratorSymbol, function () { return this; }), exports.AsyncIterator = AsyncIterator, exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) { void 0 === PromiseImpl && (PromiseImpl = Promise); var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl); return exports.isGeneratorFunction(outerFn) ? iter : iter.next().then(function (result) { return result.done ? result.value : iter.next(); }); }, defineIteratorMethods(Gp), define(Gp, toStringTagSymbol, "Generator"), define(Gp, iteratorSymbol, function () { return this; }), define(Gp, "toString", function () { return "[object Generator]"; }), exports.keys = function (val) { var object = Object(val), keys = []; for (var key in object) keys.push(key); return keys.reverse(), function next() { for (; keys.length;) { var key = keys.pop(); if (key in object) return next.value = key, next.done = !1, next; } return next.done = !0, next; }; }, exports.values = values, Context.prototype = { constructor: Context, reset: function (skipTempReset) { if (this.prev = 0, this.next = 0, this.sent = this._sent = undefined, this.done = !1, this.delegate = null, this.method = "next", this.arg = undefined, this.tryEntries.forEach(resetTryEntry), !skipTempReset) for (var name in this) "t" === name.charAt(0) && hasOwn.call(this, name) && !isNaN(+name.slice(1)) && (this[name] = undefined); }, stop: function () { this.done = !0; var rootRecord = this.tryEntries[0].completion; if ("throw" === rootRecord.type) throw rootRecord.arg; return this.rval; }, dispatchException: function (exception) { if (this.done) throw exception; var context = this; function handle(loc, caught) { return record.type = "throw", record.arg = exception, context.next = loc, caught && (context.method = "next", context.arg = undefined), !!caught; } for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i], record = entry.completion; if ("root" === entry.tryLoc) return handle("end"); if (entry.tryLoc <= this.prev) { var hasCatch = hasOwn.call(entry, "catchLoc"), hasFinally = hasOwn.call(entry, "finallyLoc"); if (hasCatch && hasFinally) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } else if (hasCatch) { if (this.prev < entry.catchLoc) return handle(entry.catchLoc, !0); } else { if (!hasFinally) throw new Error("try statement without catch or finally"); if (this.prev < entry.finallyLoc) return handle(entry.finallyLoc); } } } }, abrupt: function (type, arg) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc <= this.prev && hasOwn.call(entry, "finallyLoc") && this.prev < entry.finallyLoc) { var finallyEntry = entry; break; } } finallyEntry && ("break" === type || "continue" === type) && finallyEntry.tryLoc <= arg && arg <= finallyEntry.finallyLoc && (finallyEntry = null); var record = finallyEntry ? finallyEntry.completion : {}; return record.type = type, record.arg = arg, finallyEntry ? (this.method = "next", this.next = finallyEntry.finallyLoc, ContinueSentinel) : this.complete(record); }, complete: function (record, afterLoc) { if ("throw" === record.type) throw record.arg; return "break" === record.type || "continue" === record.type ? this.next = record.arg : "return" === record.type ? (this.rval = this.arg = record.arg, this.method = "return", this.next = "end") : "normal" === record.type && afterLoc && (this.next = afterLoc), ContinueSentinel; }, finish: function (finallyLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.finallyLoc === finallyLoc) return this.complete(entry.completion, entry.afterLoc), resetTryEntry(entry), ContinueSentinel; } }, catch: function (tryLoc) { for (var i = this.tryEntries.length - 1; i >= 0; --i) { var entry = this.tryEntries[i]; if (entry.tryLoc === tryLoc) { var record = entry.completion; if ("throw" === record.type) { var thrown = record.arg; resetTryEntry(entry); } return thrown; } } throw new Error("illegal catch attempt"); }, delegateYield: function (iterable, resultName, nextLoc) { return this.delegate = { iterator: values(iterable), resultName: resultName, nextLoc: nextLoc }, "next" === this.method && (this.arg = undefined), ContinueSentinel; } }, exports; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var tmpVec3a = vec3.create(); var tmpVec3b = vec3.create(); var tmpVec3c = vec3.create(); var tmpMat4 = mat4.create(); /** * pick shape(s) with Mouse/Touch event * * 1. find AABB with r-tree * 2. do math calculation with geometry in an accurate way */ var CanvasPickerPlugin = /*#__PURE__*/function () { function CanvasPickerPlugin() { var _this = this; this.context = void 0; this.runtime = void 0; this.isHit = function (displayObject, position, worldTransform, isClipPath) { // use picker for current shape's type var pick = _this.context.pointInPathPickerFactory[displayObject.nodeName]; if (pick) { // invert with world matrix var invertWorldMat = mat4.invert(tmpMat4, worldTransform); // transform client position to local space, do picking in local space var localPosition = vec3.transformMat4(tmpVec3b, vec3.set(tmpVec3c, position[0], position[1], 0), invertWorldMat); // account for anchor var _displayObject$getGeo = displayObject.getGeometryBounds(), halfExtents = _displayObject$getGeo.halfExtents; var anchor = displayObject.parsedStyle.anchor; localPosition[0] += (anchor && anchor[0] || 0) * halfExtents[0] * 2; localPosition[1] += (anchor && anchor[1] || 0) * halfExtents[1] * 2; if (pick(displayObject, new Point(localPosition[0], localPosition[1]), isClipPath, _this.isPointInPath, _this.context, _this.runtime)) { return true; } } return false; }; /** * use native picking method * @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/isPointInPath */ this.isPointInPath = function (displayObject, position) { var context = _this.runtime.offscreenCanvas.getOrCreateContext(_this.context.config.offscreenCanvas); var generatePath = _this.context.pathGeneratorFactory[displayObject.nodeName]; if (generatePath) { context.beginPath(); generatePath(context, displayObject.parsedStyle); context.closePath(); } return context.isPointInPath(position.x, position.y); }; } var _proto = CanvasPickerPlugin.prototype; _proto.apply = function apply(context, runtime) { var _renderingContext$roo, _this2 = this; var renderingService = context.renderingService, renderingContext = context.renderingContext; this.context = context; this.runtime = runtime; var document = (_renderingContext$roo = renderingContext.root) === null || _renderingContext$roo === void 0 ? void 0 : _renderingContext$roo.ownerDocument; renderingService.hooks.pick.tapPromise(CanvasPickerPlugin.tag, /*#__PURE__*/function () { var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(result) { return _regeneratorRuntime().wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: return _context.abrupt("return", _this2.pick(document, result)); case 1: case "end": return _context.stop(); } }, _callee); })); return function (_x) { return _ref.apply(this, arguments); }; }()); renderingService.hooks.pickSync.tap(CanvasPickerPlugin.tag, function (result) { return _this2.pick(document, result); }); }; _proto.pick = function pick(document, result) { var topmost = result.topmost, _result$position = result.position, x = _result$position.x, y = _result$position.y; // position in world space var position = vec3.set(tmpVec3a, x, y, 0); // query by AABB first with spatial index(r-tree) var hitTestList = document.elementsFromBBox(position[0], position[1], position[0], position[1]); // test with clip path & origin shape // @see https://github.com/antvis/g/issues/1064 var pickedDisplayObjects = []; for (var _iterator = _createForOfIteratorHelperLoose(hitTestList), _step; !(_step = _iterator()).done;) { var displayObject = _step.value; var worldTransform = displayObject.getWorldTransform(); var isHitOriginShape = this.isHit(displayObject, position, worldTransform, false); if (isHitOriginShape) { // should look up in the ancestor node var clipped = findClosestClipPathTarget(displayObject); if (clipped) { var clipPath = clipped.parsedStyle.clipPath; var isHitClipPath = this.isHit(clipPath, position, clipPath.getWorldTransform(), true); if (isHitClipPath) { if (topmost) { result.picked = [displayObject]; return result; } else { pickedDisplayObjects.push(displayObject); } } } else { if (topmost) { result.picked = [displayObject]; return result; } else { pickedDisplayObjects.push(displayObject); } } } } result.picked = pickedDisplayObjects; return result; }; return CanvasPickerPlugin; }(); CanvasPickerPlugin.tag = 'CanvasPicker'; /** * 两点之间的距离 * @param {number} x1 起始点 x * @param {number} y1 起始点 y * @param {number} x2 结束点 x * @param {number} y2 结束点 y * @return {number} 距离 */ function distance(x1, y1, x2, y2) { var dx = x1 - x2; var dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); } function isNumberEqual(v1, v2) { return Math.abs(v1 - v2) < 0.001; } function getBBoxByArray(xArr, yArr) { var minX = Math.min.apply(Math, xArr); var minY = Math.min.apply(Math, yArr); var maxX = Math.max.apply(Math, xArr); var maxY = Math.max.apply(Math, yArr); return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; } function piMod(angle) { return (angle + Math.PI * 2) % (Math.PI * 2); } var line = { /** * 计算线段的包围盒 * @param {number} x1 起始点 x * @param {number} y1 起始点 y * @param {number} x2 结束点 x * @param {number} y2 结束点 y * @return {object} 包围盒对象 */ box: function box(x1, y1, x2, y2) { return getBBoxByArray([x1, x2], [y1, y2]); }, /** * 线段的长度 * @param {number} x1 起始点 x * @param {number} y1 起始点 y * @param {number} x2 结束点 x * @param {number} y2 结束点 y * @return {number} 距离 */ length: function length(x1, y1, x2, y2) { return distance(x1, y1, x2, y2); }, /** * 根据比例获取点 * @param {number} x1 起始点 x * @param {number} y1 起始点 y * @param {number} x2 结束点 x * @param {number} y2 结束点 y * @param {number} t 指定比例 * @return {object} 包含 x, y 的点 */ pointAt: function pointAt(x1, y1, x2, y2, t) { return { x: (1 - t) * x1 + t * x2, y: (1 - t) * y1 + t * y2 }; }, /** * 点到线段的距离 * @param {number} x1 起始点 x * @param {number} y1 起始点 y * @param {number} x2 结束点 x * @param {number} y2 结束点 y * @param {number} x 测试点 x * @param {number} y 测试点 y * @return {number} 距离 */ pointDistance: function pointDistance(x1, y1, x2, y2, x, y) { // 投影距离 x1, y1 的向量,假设 p, p1, p2 三个点,投影点为 a // p1a = p1p.p1p2/|p1p2| * (p1p 的单位向量) var cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1); if (cross < 0) { return distance(x1, y1, x, y); } var lengthSquare = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); if (cross > lengthSquare) { return distance(x2, y2, x, y); } return this.pointToLine(x1, y1, x2, y2, x, y); }, /** * 点到直线的距离,而不是点到线段的距离 * @param {number} x1 起始点 x * @param {number} y1 起始点 y * @param {number} x2 结束点 x * @param {number} y2 结束点 y * @param {number} x 测试点 x * @param {number} y 测试点 y * @return {number} 距离 */ pointToLine: function pointToLine(x1, y1, x2, y2, x, y) { var d = [x2 - x1, y2 - y1]; // 如果端点相等,则判定点到点的距离 if (vec2.exactEquals(d, [0, 0])) { return Math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1)); } var u = [-d[1], d[0]]; vec2.normalize(u, u); var a = [x - x1, y - y1]; return Math.abs(vec2.dot(a, u)); }, /** * 线段的角度 * @param {number} x1 起始点 x * @param {number} y1 起始点 y * @param {number} x2 结束点 x * @param {number} y2 结束点 y * @return {number} 导数 */ tangentAngle: function tangentAngle(x1, y1, x2, y2) { return Math.atan2(y2 - y1, x2 - x1); } }; var EPSILON = 0.0001; /** * 使用牛顿切割法求最近的点 * @param {number[]} xArr 点的 x 数组 * @param {number[]} yArr 点的 y 数组 * @param {number} x 指定的点 x * @param {number} y 指定的点 y * @param {Function} tCallback 差值函数 */ function nearestPoint(xArr, yArr, x, y, tCallback, length) { var t = -1; var d = Infinity; var v0 = [x, y]; var segNum = 20; if (length && length > 200) { segNum = length / 10; } var increaseRate = 1 / segNum; var interval = increaseRate / 10; for (var i = 0; i <= segNum; i++) { var _t = i * increaseRate; var v1 = [tCallback.apply(void 0, xArr.concat([_t])), tCallback.apply(void 0, yArr.concat([_t]))]; var d1 = distance(v0[0], v0[1], v1[0], v1[1]); if (d1 < d) { t = _t; d = d1; } } // 提前终止 if (t === 0) { return { x: xArr[0], y: yArr[0] }; } if (t === 1) { var count = xArr.length; return { x: xArr[count - 1], y: yArr[count - 1] }; } d = Infinity; for (var _i = 0; _i < 32; _i++) { if (interval < EPSILON) { break; } var prev = t - interval; var next = t + interval; var _v = [tCallback.apply(void 0, xArr.concat([prev])), tCallback.apply(void 0, yArr.concat([prev]))]; var _d = distance(v0[0], v0[1], _v[0], _v[1]); if (prev >= 0 && _d < d) { t = prev; d = _d; } else { var v2 = [tCallback.apply(void 0, xArr.concat([next])), tCallback.apply(void 0, yArr.concat([next]))]; var d2 = distance(v0[0], v0[1], v2[0], v2[1]); if (next <= 1 && d2 < d) { t = next; d = d2; } else { interval *= 0.5; } } } return { x: tCallback.apply(void 0, xArr.concat([t])), y: tCallback.apply(void 0, yArr.concat([t])) }; } // 近似求解 https://community.khronos.org/t/3d-cubic-bezier-segment-length/62363/2 function snapLength(xArr, yArr) { var totalLength = 0; var count = xArr.length; for (var i = 0; i < count; i++) { var x = xArr[i]; var y = yArr[i]; var nextX = xArr[(i + 1) % count]; var nextY = yArr[(i + 1) % count]; totalLength += distance(x, y, nextX, nextY); } return totalLength / 2; } // 差值公式 function quadraticAt(p0, p1, p2, t) { var onet = 1 - t; return onet * onet * p0 + 2 * t * onet * p1 + t * t * p2; } // 求极值 function extrema(p0, p1, p2) { var a = p0 + p2 - 2 * p1; if (isNumberEqual(a, 0)) { return [0.5]; } var rst = (p0 - p1) / a; if (rst <= 1 && rst >= 0) { return [rst]; } return []; } function derivativeAt(p0, p1, p2, t) { return 2 * (1 - t) * (p1 - p0) + 2 * t * (p2 - p1); } // 分割贝塞尔曲线 function divideQuadratic(x1, y1, x2, y2, x3, y3, t) { // 划分点 var xt = quadraticAt(x1, x2, x3, t); var yt = quadraticAt(y1, y2, y3, t); // 分割的第一条曲线的控制点 var controlPoint1 = line.pointAt(x1, y1, x2, y2, t); // 分割的第二条曲线的控制点 var controlPoint2 = line.pointAt(x2, y2, x3, y3, t); return [[x1, y1, controlPoint1.x, controlPoint1.y, xt, yt], [xt, yt, controlPoint2.x, controlPoint2.y, x3, y3]]; } // 使用迭代法取贝塞尔曲线的长度 function quadraticLength(x1, y1, x2, y2, x3, y3, iterationCount) { if (iterationCount === 0) { return (distance(x1, y1, x2, y2) + distance(x2, y2, x3, y3) + distance(x1, y1, x3, y3)) / 2; } var quadratics = divideQuadratic(x1, y1, x2, y2, x3, y3, 0.5); var left = quadratics[0]; var right = quadratics[1]; left.push(iterationCount - 1); right.push(iterationCount - 1); return quadraticLength.apply(void 0, left) + quadraticLength.apply(void 0, right); } var quadratic = { box: function box(x1, y1, x2, y2, x3, y3) { var xExtrema = extrema(x1, x2, x3)[0]; var yExtrema = extrema(y1, y2, y3)[0]; // 控制点不加入 box 的计算 var xArr = [x1, x3]; var yArr = [y1, y3]; if (xExtrema !== undefined) { xArr.push(quadraticAt(x1, x2, x3, xExtrema)); } if (yExtrema !== undefined) { yArr.push(quadraticAt(y1, y2, y3, yExtrema)); } return getBBoxByArray(xArr, yArr); }, length: function length(x1, y1, x2, y2, x3, y3) { return quadraticLength(x1, y1, x2, y2, x3, y3, 3); }, nearestPoint: function nearestPoint$1(x1, y1, x2, y2, x3, y3, x0, y0) { return nearestPoint([x1, x2, x3], [y1, y2, y3], x0, y0, quadraticAt); }, pointDistance: function pointDistance(x1, y1, x2, y2, x3, y3, x0, y0) { var point = this.nearestPoint(x1, y1, x2, y2, x3, y3, x0, y0); return distance(point.x, point.y, x0, y0); }, interpolationAt: quadraticAt, pointAt: function pointAt(x1, y1, x2, y2, x3, y3, t) { return { x: quadraticAt(x1, x2, x3, t), y: quadraticAt(y1, y2, y3, t) }; }, divide: function divide(x1, y1, x2, y2, x3, y3, t) { return divideQuadratic(x1, y1, x2, y2, x3, y3, t); }, tangentAngle: function tangentAngle(x1, y1, x2, y2, x3, y3, t) { var dx = derivativeAt(x1, x2, x3, t); var dy = derivativeAt(y1, y2, y3, t); var angle = Math.atan2(dy, dx); return piMod(angle); } }; function cubicAt(p0, p1, p2, p3, t) { var onet = 1 - t; // t * t * t 的性能大概是 Math.pow(t, 3) 的三倍 return onet * onet * onet * p0 + 3 * p1 * t * onet * onet + 3 * p2 * t * t * onet + p3 * t * t * t; } function derivativeAt$1(p0, p1, p2, p3, t) { var onet = 1 - t; return 3 * (onet * onet * (p1 - p0) + 2 * onet * t * (p2 - p1) + t * t * (p3 - p2)); } function extrema$1(p0, p1, p2, p3) { var a = -3 * p0 + 9 * p1 - 9 * p2 + 3 * p3; var b = 6 * p0 - 12 * p1 + 6 * p2; var c = 3 * p1 - 3 * p0; var extremas = []; var t1; var t2; var discSqrt; if (isNumberEqual(a, 0)) { if (!isNumberEqual(b, 0)) { t1 = -c / b; if (t1 >= 0 && t1 <= 1) { extremas.push(t1); } } } else { var disc = b * b - 4 * a * c; if (isNumberEqual(disc, 0)) { extremas.push(-b / (2 * a)); } else if (disc > 0) { discSqrt = Math.sqrt(disc); t1 = (-b + discSqrt) / (2 * a); t2 = (-b - discSqrt) / (2 * a); if (t1 >= 0 && t1 <= 1) { extremas.push(t1); } if (t2 >= 0 && t2 <= 1) { extremas.push(t2); } } } return extremas; } // 分割贝塞尔曲线 function divideCubic(x1, y1, x2, y2, x3, y3, x4, y4, t) { // 划分点 var xt = cubicAt(x1, x2, x3, x4, t); var yt = cubicAt(y1, y2, y3, y4, t); // 计算两点之间的差值点 var c1 = line.pointAt(x1, y1, x2, y2, t); var c2 = line.pointAt(x2, y2, x3, y3, t); var c3 = line.pointAt(x3, y3, x4, y4, t); var c12 = line.pointAt(c1.x, c1.y, c2.x, c2.y, t); var c23 = line.pointAt(c2.x, c2.y, c3.x, c3.y, t); return [[x1, y1, c1.x, c1.y, c12.x, c12.y, xt, yt], [xt, yt, c23.x, c23.y, c3.x, c3.y, x4, y4]]; } // 使用迭代法取贝塞尔曲线的长度,二阶和三阶分开写,更清晰和便于调试 function cubicLength(x1, y1, x2, y2, x3, y3, x4, y4, iterationCount) { if (iterationCount === 0) { return snapLength([x1, x2, x3, x4], [y1, y2, y3, y4]); } var cubics = divideCubic(x1, y1, x2, y2, x3, y3, x4, y4, 0.5); var left = [].concat(cubics[0], [iterationCount - 1]); var right = [].concat(cubics[1], [iterationCount - 1]); return cubicLength.apply(void 0, left) + cubicLength.apply(void 0, right); } var cubic = { extrema: extrema$1, box: function box(x1, y1, x2, y2, x3, y3, x4, y4) { var xArr = [x1, x4]; var yArr = [y1, y4]; var xExtrema = extrema$1(x1, x2, x3, x4); var yExtrema = extrema$1(y1, y2, y3, y4); for (var i = 0; i < xExtrema.length; i++) { xArr.push(cubicAt(x1, x2, x3, x4, xExtrema[i])); } for (var _i = 0; _i < yExtrema.length; _i++) { yArr.push(cubicAt(y1, y2, y3, y4, yExtrema[_i])); } return getBBoxByArray(xArr, yArr); }, length: function length(x1, y1, x2, y2, x3, y3, x4, y4) { // 迭代三次,划分成 8 段求长度 return cubicLength(x1, y1, x2, y2, x3, y3, x4, y4, 3); }, nearestPoint: function nearestPoint$1(x1, y1, x2, y2, x3, y3, x4, y4, x0, y0, length) { return nearestPoint([x1, x2, x3, x4], [y1, y2, y3, y4], x0, y0, cubicAt, length); }, pointDistance: function pointDistance(x1, y1, x2, y2, x3, y3, x4, y4, x0, y0, length) { var point = this.nearestPoint(x1, y1, x2, y2, x3, y3, x4, y4, x0, y0, length); return distance(point.x, point.y, x0, y0); }, interpolationAt: cubicAt, pointAt: function pointAt(x1, y1, x2, y2, x3, y3, x4, y4, t) { return { x: cubicAt(x1, x2, x3, x4, t), y: cubicAt(y1, y2, y3, y4, t) }; }, divide: function divide(x1, y1, x2, y2, x3, y3, x4, y4, t) { return divideCubic(x1, y1, x2, y2, x3, y3, x4, y4, t); }, tangentAngle: function tangentAngle(x1, y1, x2, y2, x3, y3, x4, y4, t) { var dx = derivativeAt$1(x1, x2, x3, x4, t); var dy = derivativeAt$1(y1, y2, y3, y4, t); return piMod(Math.atan2(dy, dx)); } }; function distance$1(x1, y1, x2, y2) { var dx = x1 - x2; var dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); } function inBox(minX, minY, width, height, x, y) { return x >= minX && x <= minX + width && y >= minY && y <= minY + height; } function inRect(minX, minY, width, height, lineWidth, x, y) { var halfWidth = lineWidth / 2; // 将四个边看做矩形来检测,比边的检测算法要快 return inBox(minX - halfWidth, minY - halfWidth, width, lineWidth, x, y) || // 上边 inBox(minX + width - halfWidth, minY - halfWidth, lineWidth, height, x, y) || // 右边 inBox(minX + halfWidth, minY + height - halfWidth, width, lineWidth, x, y) || // 下边 inBox(minX - halfWidth, minY + halfWidth, lineWidth, height, x, y); // 左边 } function inArc(cx, cy, r, startAngle, endAngle, lineWidth, x, y) { var angle = (Math.atan2(y - cy, x - cx) + Math.PI * 2) % (Math.PI * 2); // 转换到 0 - 2 * Math.PI 之间 // if (angle < startAngle || angle > endAngle) { // return false; // } var point = { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) }; return distance$1(point.x, point.y, x, y) <= lineWidth / 2; } function inLine(x1, y1, x2, y2, lineWidth, x, y) { var minX = Math.min(x1, x2); var maxX = Math.max(x1, x2); var minY = Math.min(y1, y2); var maxY = Math.max(y1, y2); var halfWidth = lineWidth / 2; // 因为目前的方案是计算点到直线的距离,而有可能会在延长线上,所以要先判断是否在包围盒内 // 这种方案会在水平或者竖直的情况下载线的延长线上有半 lineWidth 的误差 if (!(x >= minX - halfWidth && x <= maxX + halfWidth && y >= minY - halfWidth && y <= maxY + halfWidth)) { return false; } // 因为已经计算了包围盒,所以仅需要计算到直线的距离即可,可以显著提升性能 return line.pointToLine(x1, y1, x2, y2, x, y) <= lineWidth / 2; } function inPolyline(points, lineWidth, x, y, isClose) { var count = points.length; if (count < 2) { return false; } for (var i = 0; i < count - 1; i++) { var x1 = points[i][0]; var y1 = points[i][1]; var x2 = points[i + 1][0]; var y2 = points[i + 1][1]; if (inLine(x1, y1, x2, y2, lineWidth, x, y)) { return true; } } // 如果封闭,则计算起始点和结束点的边 if (isClose) { var first = points[0]; var last = points[count - 1]; if (inLine(first[0], first[1], last[0], last[1], lineWidth, x, y)) { return true; } } return false; } // 多边形的射线检测,参考:https://blog.csdn.net/WilliamSun0122/article/details/77994526 var tolerance = 1e-6; // 三态函数,判断两个double在eps精度下的大小关系 function dcmp(x) { if (Math.abs(x) < tolerance) { return 0; } return x < 0 ? -1 : 1; } // 判断点Q是否在p1和p2的线段上 function onSegment(p1, p2, q) { if ((q[0] - p1[0]) * (p2[1] - p1[1]) === (p2[0] - p1[0]) * (q[1] - p1[1]) && Math.min(p1[0], p2[0]) <= q[0] && q[0] <= Math.max(p1[0], p2[0]) && Math.min(p1[1], p2[1]) <= q[1] && q[1] <= Math.max(p1[1], p2[1])) { return true; } return false; } // 判断点P在多边形内-射线法 function inPolygon(points, x, y) { var isHit = false; var n = points.length; if (n <= 2) { // svg 中点小于 3 个时,不显示,也无法被拾取 return false; } for (var i = 0; i < n; i++) { var p1 = points[i]; var p2 = points[(i + 1) % n]; if (onSegment(p1, p2, [x, y])) { // 点在多边形一条边上 return true; } // 前一个判断min(p1[1],p2[1])