diff --git a/src/services/editor.service.ts b/src/services/editor.service.ts index c128938..ec38d12 100644 --- a/src/services/editor.service.ts +++ b/src/services/editor.service.ts @@ -798,7 +798,6 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void { const { x: dx2 = 0, y: dy2 = 0 } = c2 ?? {}; const [c1x, c1y] = [x1 + dx1 * s, y1 + dy1 * s]; const [c2x, c2y] = [x2 + dx2 * s, y2 + dy2 * s]; - const t = direction < 0 ? 0.55 : 0.45; ctx.save(); ctx.beginPath(); @@ -829,51 +828,30 @@ function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void { ctx.beginPath(); ctx.setLineDash([0]); - let [ax, ay] = [0, 0]; - let r = 0; - switch (type) { - case MapRouteType.直线: - { - ax = x1 + (x2 - x1) * t; - ay = y1 + (y2 - y1) * t; - r = Math.atan2(y2 - y1, x2 - x1); + const { dx, dy, r } = (() => { + switch (type) { + case MapRouteType.直线: { + const t = direction < 0 ? 0.55 : 0.45; + const dx = x1 + (x2 - x1) * t; + const dy = y1 + (y2 - y1) * t; + const r = Math.atan2(y2 - y1, x2 - x1) + (direction > 0 ? Math.PI : 0); + return { dx, dy, r }; } - break; - case MapRouteType.二阶贝塞尔曲线: - { - const t1x = x1 + (c1x - x1) * t; - const t1y = y1 + (c1y - y1) * t; - const t2x = c1x + (x2 - c1x) * t; - const t2y = c1y + (y2 - c1y) * t; - ax = t1x + (t2x - t1x) * t; - ay = t1y + (t2y - t1y) * t; - r = Math.atan2(t2y - t1y, t2x - t1x); + case MapRouteType.二阶贝塞尔曲线: { + const { x: dx, y: dy, t } = getBezier2Center(p1, { x: c1x, y: c1y }, p2); + const r = getBezier2Tange(p1, { x: c1x, y: c1y }, p2, t) + (direction > 0 ? Math.PI : 0); + return { dx, dy, r }; } - break; - case MapRouteType.三阶贝塞尔曲线: - { - const t1x = x1 + (c1x - x1) * t; - const t1y = y1 + (c1y - y1) * t; - const t2x = c1x + (c2x - c1x) * t; - const t2y = c1y + (c2y - c1y) * t; - const t3x = c2x + (x2 - c2x) * t; - const t3y = c2y + (y2 - c2y) * t; - const t12x = t1x + (t2x - t1x) * t; - const t12y = t1y + (t2y - t1y) * t; - const t23x = t2x + (t3x - t2x) * t; - const t23y = t2y + (t3y - t2y) * t; - ax = t12x + (t23x - t12x) * t; - ay = t12y + (t23y - t12y) * t; - r = Math.atan2(t23y - t12y, t23x - t12x); + case MapRouteType.三阶贝塞尔曲线: { + const { x: dx, y: dy, t } = getBezier3Center(p1, { x: c1x, y: c1y }, { x: c2x, y: c2y }, p2); + const r = getBezier3Tange(p1, { x: c1x, y: c1y }, { x: c2x, y: c2y }, p2, t) + (direction > 0 ? Math.PI : 0); + return { dx, dy, r }; } - break; - default: - break; - } - ctx.translate(ax, ay); - if (direction > 0) { - r += Math.PI; - } + default: + return { dx: 0, dy: 0, r: 0 }; + } + })(); + ctx.translate(dx, dy); ctx.moveTo(Math.cos(r + Math.PI / 5) * s * 10, Math.sin(r + Math.PI / 5) * s * 10); ctx.lineTo(0, 0); ctx.lineTo(Math.cos(r - Math.PI / 5) * s * 10, Math.sin(r - Math.PI / 5) * s * 10); @@ -967,3 +945,71 @@ function drawRobot(ctx: CanvasRenderingContext2D, pen: MapPen): void { ctx.restore(); } //#endregion + +//#region 辅助函数 +function getBezier2Center(p1: Point, c1: Point, p2: Point): Point & { t: number } { + const fn = (t: number) => { + const x = (1 - t) ** 2 * p1.x + 2 * (1 - t) * t * c1.x + t ** 2 * p2.x; + const y = (1 - t) ** 2 * p1.y + 2 * (1 - t) * t * c1.y + t ** 2 * p2.y; + return { x, y }; + }; + return calcBezierCenter(fn); +} +function getBezier2Tange(p1: Point, c1: Point, p2: Point, t: number): number { + const dx = 2 * (1 - t) * (c1.x - p1.x) + 2 * t * (p2.x - c1.x); + const dy = 2 * (1 - t) * (c1.y - p1.y) + 2 * t * (p2.y - c1.y); + return Math.atan2(dy, dx); +} + +function getBezier3Center(p1: Point, c1: Point, c2: Point, p2: Point): Point & { t: number } { + const fn = (t: number) => { + const x = (1 - t) ** 3 * p1.x + 3 * (1 - t) ** 2 * t * c1.x + 3 * (1 - t) * t ** 2 * c2.x + t ** 3 * p2.x; + const y = (1 - t) ** 3 * p1.y + 3 * (1 - t) ** 2 * t * c1.y + 3 * (1 - t) * t ** 2 * c2.y + t ** 3 * p2.y; + return { x, y }; + }; + return calcBezierCenter(fn); +} +function getBezier3Tange(p1: Point, c1: Point, c2: Point, p2: Point, t: number): number { + const t1 = 3 * Math.pow(1 - t, 2); + const t2 = 6 * (1 - t) * t; + const t3 = 3 * Math.pow(t, 2); + + const dx = t1 * (c1.x - p1.x) + t2 * (c2.x - c1.x) + t3 * (p2.x - c2.x); + const dy = t1 * (c1.y - p1.y) + t2 * (c2.y - c1.y) + t3 * (p2.y - c2.y); + return Math.atan2(dy, dx); +} + +function calcBezierCenter(bezierFn: (t: number) => Point): Point & { t: number } { + const count = 23; + + let length = 0; + let temp = bezierFn(0); + const samples = Array.from({ length: count }, (_, i) => { + const t = (i + 1) / count; + const point = bezierFn(t); + const dx = point.x - temp.x; + const dy = point.y - temp.y; + length += Math.sqrt(dx * dx + dy * dy); + temp = point; + return { ...point, t }; + }); + + const target = length / 2; + let accumulated = 0; + for (let i = 0; i < samples.length - 1; i++) { + const p1 = samples[i]; + const p2 = samples[i + 1]; + const segment = Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2); + if (accumulated + segment >= target) { + const ratio = (target - accumulated) / segment; + return { + x: p1.x + (p2.x - p1.x) * ratio, + y: p1.y + (p2.y - p1.y) * ratio, + t: p1.t + ratio * (p2.t - p1.t), + }; + } + accumulated += segment; + } + return samples[samples.length - 1]; +} +//#endregion