|
|
@@ -1039,6 +1039,28 @@ export default class MathUtil {
|
|
|
return v1.x * v2.y - v1.y * v2.x;
|
|
|
}
|
|
|
|
|
|
+ // 两点相减
|
|
|
+ pointMinus(v1, v2) {
|
|
|
+ return {
|
|
|
+ x: v1.x - v2.x,
|
|
|
+ y: v1.y - v2.y
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 两点相加
|
|
|
+ pointPlus(v1, v2) {
|
|
|
+ return {
|
|
|
+ x: v1.x + v2.x,
|
|
|
+ y: v1.y + v2.y
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 点放大
|
|
|
+ pointScale(v, a) {
|
|
|
+ return {
|
|
|
+ x: v.x * a,
|
|
|
+ y: v.y * a
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
clamp(value, min, max) {
|
|
|
return Math.max(min, Math.min(max, value));
|
|
|
}
|
|
|
@@ -1047,29 +1069,206 @@ export default class MathUtil {
|
|
|
return v.x * v.x + v.y * v.y;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ // 当前点 下一个点 下下个点
|
|
|
+ getCurvesControls(p1, pt, p2, scale = 0.3) {
|
|
|
+ const vec1T = mathUtil.pointMinus(p1, pt);
|
|
|
+ const vecT2 = mathUtil.pointMinus(p1, pt);
|
|
|
+ const len1 = Math.hypot(vec1T.x, vec1T.y);
|
|
|
+ const len2 = Math.hypot(vecT2.x, vecT2.y);
|
|
|
+ const v = len1 / len2;
|
|
|
+
|
|
|
+ let delta;
|
|
|
+ if (v > 1) {
|
|
|
+ delta = mathUtil.pointMinus(
|
|
|
+ p1,
|
|
|
+ mathUtil.pointPlus(pt, mathUtil.pointScale(mathUtil.pointMinus(p2, pt), (1 / v))),
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ delta = mathUtil.pointMinus(
|
|
|
+ mathUtil.pointPlus(pt, mathUtil.pointScale(mathUtil.pointMinus(p1, pt), v)),
|
|
|
+ p2,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ delta = mathUtil.pointScale(delta, scale);
|
|
|
+
|
|
|
+ const control1 = {
|
|
|
+ x: mathUtil.pointPlus(pt, delta).x,
|
|
|
+ y: mathUtil.pointPlus(pt, delta).y,
|
|
|
+ };
|
|
|
+ const control2 = {
|
|
|
+ x: mathUtil.pointMinus(pt, delta).x,
|
|
|
+ y: mathUtil.pointMinus(pt, delta).y,
|
|
|
+ };
|
|
|
+ return {control1, control2}
|
|
|
+ }
|
|
|
+
|
|
|
getCurvesByPoints(points, scale = 0.2) {
|
|
|
const curves = [];
|
|
|
- for (let i = 1; i < points.length; i++) {
|
|
|
- const prev1Index = i - 1
|
|
|
- const prev2Index = i === 1 ? prev1Index : i - 2;
|
|
|
- const nextIndex = i === points.length - 1 ? points.length - 1 : i + 1
|
|
|
- const { x: nowX, y: nowY } = points[i];
|
|
|
- const { x: last1X, y: last1Y } = points[prev1Index]
|
|
|
- const { x: last2X, y: last2Y } = points[prev2Index]
|
|
|
- const { x: nextX, y: nextY } = points[nextIndex]
|
|
|
- const cAx = last1X + (nowX - last2X) * scale,
|
|
|
- cAy = last1Y + (nowY - last2Y) * scale,
|
|
|
- cBx = nowX - (nextX - last1X) * scale,
|
|
|
- cBy = nowY - (nextY - last1Y) * scale
|
|
|
-
|
|
|
+ let preControl1, preControl2
|
|
|
+ for (let i = 0; i < points.length - 2; i++) {
|
|
|
+ const {control1, control2} = mathUtil.getCurvesControls(
|
|
|
+ points[i],
|
|
|
+ points[i + 1],
|
|
|
+ points[i + 2],
|
|
|
+ scale
|
|
|
+ )
|
|
|
curves.push({
|
|
|
- start: i === 1 ? {x: points[0].x, y: points[0].y} : { x: cAx, y: cAy },
|
|
|
- control: { x: nowX, y: nowY },
|
|
|
- end: { x: cBx, y: cBy },
|
|
|
+ start: points[i],
|
|
|
+ end: points[i + 1],
|
|
|
+ controls: i === 0 ? [control1] : [preControl2, control1]
|
|
|
})
|
|
|
+ if (i + 2 === points.length - 1) {
|
|
|
+ curves.push({
|
|
|
+ start: points[i + 1],
|
|
|
+ controls: [control2],
|
|
|
+ end: points[i + 2],
|
|
|
+ })
|
|
|
+ }
|
|
|
+ preControl1 = control1
|
|
|
+ preControl2 = control2
|
|
|
}
|
|
|
+ curves.push({
|
|
|
+ start: points[points.length - 2],
|
|
|
+ controls: [preControl2],
|
|
|
+ end: points[points.length - 1],
|
|
|
+ })
|
|
|
return curves
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 已知四个控制点,及曲线中的某一个点的 x/y,反推求 t
|
|
|
+ * @param {number} x1 起点 x/y
|
|
|
+ * @param {number} x2 控制点1 x/y
|
|
|
+ * @param {number} x3 控制点2 x/y
|
|
|
+ * @param {number} x4 终点 x/y
|
|
|
+ * @param {number} X 曲线中的某个点 x/y
|
|
|
+ * @returns {number[]} t[]
|
|
|
+ */
|
|
|
+ getThreeBezierT(x1, x2, x3, x4, X) {
|
|
|
+ const a = -x1 + 3 * x2 - 3 * x3 + x4
|
|
|
+ const b = 3 * x1 - 6 * x2 + 3 * x3
|
|
|
+ const c = -3 * x1 + 3 * x2
|
|
|
+ const d = x1 - X
|
|
|
+
|
|
|
+ // 盛金公式, 预先需满足, a !== 0
|
|
|
+ // 判别式
|
|
|
+ const A = Math.pow(b, 2) - 3 * a * c
|
|
|
+ const B = b * c - 9 * a * d
|
|
|
+ const C = Math.pow(c, 2) - 3 * b * d
|
|
|
+ const delta = Math.pow(B, 2) - 4 * A * C
|
|
|
+
|
|
|
+ let t1 = -100, t2 = -100, t3 = -100
|
|
|
+
|
|
|
+ // 3个相同实数根
|
|
|
+ if (A === B && A === 0) {
|
|
|
+ t1 = -b / (3 * a)
|
|
|
+ t2 = -c / b
|
|
|
+ t3 = -3 * d / c
|
|
|
+ return [t1, t2, t3]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1个实数根和1对共轭复数根
|
|
|
+ if (delta > 0) {
|
|
|
+ const v = Math.pow(B, 2) - 4 * A * C
|
|
|
+ const xsv = v < 0 ? -1 : 1
|
|
|
+
|
|
|
+ const m1 = A * b + 3 * a * (-B + (v * xsv) ** (1 / 2) * xsv) / 2
|
|
|
+ const m2 = A * b + 3 * a * (-B - (v * xsv) ** (1 / 2) * xsv) / 2
|
|
|
+
|
|
|
+ const xs1 = m1 < 0 ? -1 : 1
|
|
|
+ const xs2 = m2 < 0 ? -1 : 1
|
|
|
+
|
|
|
+ t1 = (-b - (m1 * xs1) ** (1 / 3) * xs1 - (m2 * xs2) ** (1 / 3) * xs2) / (3 * a)
|
|
|
+ // 涉及虚数,可不考虑。i ** 2 = -1
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3个实数根
|
|
|
+ if (delta === 0) {
|
|
|
+ const K = B / A
|
|
|
+ t1 = -b / a + K
|
|
|
+ t2 = t3 = -K / 2
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3个不相等实数根
|
|
|
+ if (delta < 0) {
|
|
|
+ const xsA = A < 0 ? -1 : 1
|
|
|
+ const T = (2 * A * b - 3 * a * B) / (2 * (A * xsA) ** (3 / 2) * xsA)
|
|
|
+ const theta = Math.acos(T)
|
|
|
+
|
|
|
+ if (A > 0 && T < 1 && T > -1) {
|
|
|
+ t1 = (-b - 2 * A ** (1 / 2) * Math.cos(theta / 3)) / (3 * a)
|
|
|
+ t2 = (-b + A ** (1 / 2) * (Math.cos(theta / 3) + 3 ** (1 / 2) * Math.sin(theta / 3))) / (3 * a)
|
|
|
+ t3 = (-b + A ** (1 / 2) * (Math.cos(theta / 3) - 3 ** (1 / 2) * Math.sin(theta / 3))) / (3 * a)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return [t1, t2, t3]
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @desc 获取三阶贝塞尔曲线的线上坐标
|
|
|
+ * B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1]
|
|
|
+ * @param {number} t 当前百分比
|
|
|
+ * @param {Array} p1 起点坐标
|
|
|
+ * @param {Array} p2 终点坐标
|
|
|
+ * @param {Array} cp1 控制点1
|
|
|
+ * @param {Array} cp2 控制点2
|
|
|
+ */
|
|
|
+ getThreeBezierPoint(t, p1, cp1, cp2, p2) {
|
|
|
+ const {x: x1, y: y1} = p1
|
|
|
+ const {x: x2, y: y2} = p2
|
|
|
+ const {x: cx1, y: cy1} = cp1
|
|
|
+ const {x: cx2, y: cy2} = cp2
|
|
|
+
|
|
|
+ const x =
|
|
|
+ x1 * (1 - t) * (1 - t) * (1 - t) +
|
|
|
+ 3 * cx1 * t * (1 - t) * (1 - t) +
|
|
|
+ 3 * cx2 * t * t * (1 - t) +
|
|
|
+ x2 * t * t * t
|
|
|
+ const y =
|
|
|
+ y1 * (1 - t) * (1 - t) * (1 - t) +
|
|
|
+ 3 * cy1 * t * (1 - t) * (1 - t) +
|
|
|
+ 3 * cy2 * t * t * (1 - t) +
|
|
|
+ y2 * t * t * t
|
|
|
+ return {x, y}
|
|
|
+ }
|
|
|
+
|
|
|
+ isAboveThreeBezier(offsetX, offsetY, curve, rang = 3) {
|
|
|
+ // 用 x 求出对应的 t,用 t 求相应位置的 y,再比较得出的 y 与 offsetY 之间的差值
|
|
|
+ const tsx = mathUtil.getThreeBezierT(curve.start.x, curve.controls[0].x, curve.controls[1].x, curve.end.x, offsetX)
|
|
|
+ for (let x = 0; x < 3; x++) {
|
|
|
+ if (tsx[x] <= 1 && tsx[x] >= 0) {
|
|
|
+ const point = mathUtil.getThreeBezierPoint(tsx[x], curve.start, curve.controls[0], curve.controls[1], curve.end)
|
|
|
+ if (Math.abs(point.y - offsetY) < rang) {
|
|
|
+ return point
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 如果上述没有结果,则用 y 求出对应的 t,再用 t 求出对应的 x,与 offsetX 进行匹配
|
|
|
+ const tsy = mathUtil.getThreeBezierT(curve.start.y, curve.controls[0].y, curve.controls[1].y, curve.end.y, offsetY)
|
|
|
+ for (let y = 0; y < 3; y++) {
|
|
|
+ if (tsy[y] <= 1 && tsy[y] >= 0) {
|
|
|
+ const point = mathUtil.getThreeBezierPoint(tsy[y], curve.start, curve.controls[0], curve.controls[1], curve.end)
|
|
|
+ if (Math.abs(point.x - offsetX) < rang) {
|
|
|
+ return point
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ isAboveCurves(pos, curves) {
|
|
|
+ for (const curve of curves) {
|
|
|
+ if (curve.controls.length === 2) {
|
|
|
+ if (mathUtil.isAboveThreeBezier(pos.x, pos.y, curve)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 二次曲线
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const mathUtil = new MathUtil();
|