@leafer-in/linker 2.3.3 → 2.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@ function getPolylineDirectionIndex(direction: IDirection): number {
23
23
  }
24
24
  }
25
25
 
26
- function getPolylinePorts(direction: IDirection, point: IPointData, target: IPointData, bounds: IBoundsData | undefined, targetBounds: IBoundsData | undefined, ports: number[]): number {
26
+ function getPolylinePorts(direction: IDirection, point: IPointData, target: IPointData, bounds?: IBoundsData, targetBounds?: IBoundsData): number[] {
27
27
  const data = directionData[getPolylineDirectionIndex(direction)]
28
28
  const dx = Math.sign(data.x - 0.5), dy = Math.sign(data.y - 0.5)
29
29
  const cx = bounds ? bounds.x + bounds.width / 2 : point.x
@@ -31,44 +31,24 @@ function getPolylinePorts(direction: IDirection, point: IPointData, target: IPoi
31
31
  const tx = targetBounds ? targetBounds.x + targetBounds.width / 2 : target.x
32
32
  const ty = targetBounds ? targetBounds.y + targetBounds.height / 2 : target.y
33
33
  const deltaX = tx - cx, deltaY = ty - cy
34
- const xSign = dx || (deltaX >= 0 ? 1 : -1)
35
- const ySign = dy || (deltaY >= 0 ? 1 : -1)
36
- const xPort = POLYLINE_X * 2 + (xSign > 0 ? 1 : 0)
37
- const yPort = POLYLINE_Y * 2 + (ySign > 0 ? 1 : 0)
38
-
39
- if (dx && !dy) {
40
- ports[0] = xPort
41
- return 1
42
- }
43
-
44
- if (dy && !dx) {
45
- ports[0] = yPort
46
- return 1
47
- }
48
-
49
- if (Math.abs(deltaX) >= Math.abs(deltaY)) {
50
- ports[0] = xPort
51
- ports[1] = yPort
52
- } else {
53
- ports[0] = yPort
54
- ports[1] = xPort
55
- }
34
+ const xPort = (dx || (deltaX >= 0 ? 1 : -1)) > 0 ? 1 : 0
35
+ const yPort = (dy || (deltaY >= 0 ? 1 : -1)) > 0 ? 3 : 2
56
36
 
57
- return 2
37
+ if (dx && !dy) return [xPort]
38
+ if (dy && !dx) return [yPort]
39
+ return Math.abs(deltaX) >= Math.abs(deltaY) ? [xPort, yPort] : [yPort, xPort]
58
40
  }
59
41
 
60
- function getPolylineExit(point: IPointData, bounds: IBoundsData | undefined, port: number, clearance: number, result: number[]): void {
42
+ function getPolylineExit(point: IPointData, bounds: IBoundsData | undefined, port: number, clearance: number): IPointData {
61
43
  const axis = port >> 1, sign = port & 1 ? 1 : -1
62
44
 
63
45
  if (axis === POLYLINE_X) {
64
- const boundary = bounds ? sign > 0 ? bounds.x + bounds.width : bounds.x : point.x
65
- result[0] = boundary + sign * clearance
66
- result[1] = point.y
67
- } else {
68
- const boundary = bounds ? sign > 0 ? bounds.y + bounds.height : bounds.y : point.y
69
- result[0] = point.x
70
- result[1] = boundary + sign * clearance
46
+ const x = bounds ? bounds.x + (sign > 0 ? bounds.width : 0) : point.x
47
+ return { x: x + sign * clearance, y: point.y }
71
48
  }
49
+
50
+ const y = bounds ? bounds.y + (sign > 0 ? bounds.height : 0) : point.y
51
+ return { x: point.x, y: y + sign * clearance }
72
52
  }
73
53
 
74
54
  function isPolylinePointBlocked(x: number, y: number, bounds: IBoundsData | undefined, clearance: number): boolean {
@@ -131,63 +111,93 @@ function getPolylineMidpointAxis(startPort: number, endPort: number, sx: number,
131
111
  return Math.abs(ex - sx) >= Math.abs(ey - sy) ? POLYLINE_X : POLYLINE_Y
132
112
  }
133
113
 
134
- // 检查中点优先路径
135
- function getPolylineMidpointRoute(result: number[], startPoint: IPointData, endPoint: IPointData, sx: number, sy: number, ex: number, ey: number, midpointAxis: number, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number): number {
136
- const midpointX = (sx + ex) / 2, midpointY = (sy + ey) / 2
137
- result.length = 0
138
- result.push(startPoint.x, startPoint.y, sx, sy)
114
+ function isPolylinePortAligned(port: number, stub: number, dx: number, dy: number): boolean {
115
+ const offset = (port >> 1) === POLYLINE_X ? dx : dy
116
+ const sign = port & 1 ? 1 : -1
117
+ return Math.abs(offset) <= POLYLINE_EPSILON ? stub > POLYLINE_EPSILON : offset * sign > 0
118
+ }
139
119
 
140
- if (midpointAxis === POLYLINE_X) {
141
- result.push(midpointX, sy, midpointX, ey)
142
- } else {
143
- result.push(sx, midpointY, ex, midpointY)
144
- }
120
+ function isPolylineMidpointAligned(port: number | undefined, stub: number, dx: number, dy: number, axis: number): boolean {
121
+ if (port === undefined) return true
122
+
123
+ // 取中点路径中实际存在的第一段,忽略重合的出口和转折点
124
+ if (axis === POLYLINE_X && Math.abs(dx / 2) > POLYLINE_EPSILON) return isPolylinePortAligned(port, stub, dx, 0)
125
+ if (axis === POLYLINE_Y && Math.abs(dy / 2) > POLYLINE_EPSILON) return isPolylinePortAligned(port, stub, 0, dy)
126
+ if (Math.abs(dx) <= POLYLINE_EPSILON && Math.abs(dy) <= POLYLINE_EPSILON) return true
127
+ return isPolylinePortAligned(port, stub, dx, dy)
128
+ }
145
129
 
146
- result.push(ex, ey, endPoint.x, endPoint.y)
130
+ // 检查实际接入方向,允许顺向接入或垂直转向,禁止与后续线段原路折返
131
+ function isPolylineApproachAligned(points: number[], port?: number): boolean {
132
+ if (port === undefined) return true
147
133
 
148
- // 只检查两端出口之间的路径,起终点连接段允许进入自身元素
149
- for (let i = 4; i < result.length - 2; i += 2) {
150
- const x1 = result[i - 2], y1 = result[i - 1]
151
- const x2 = result[i], y2 = result[i + 1]
134
+ const last = points.length - 2, x = points[last], y = points[last + 1]
152
135
 
153
- if (isPolylineSegmentBlocked(x1, y1, x2, y2, startBounds, clearance) ||
154
- isPolylineSegmentBlocked(x1, y1, x2, y2, endBounds, clearance)) return Infinity
136
+ for (let i = last - 2; i >= 0; i -= 2) {
137
+ const dx = points[i] - x, dy = points[i + 1] - y
138
+ if (Math.abs(dx) > POLYLINE_EPSILON || Math.abs(dy) > POLYLINE_EPSILON) return isPolylinePortAligned(port, 1, dx, dy)
155
139
  }
156
140
 
157
- return getPolylineScore(result, clearance)
141
+ return true
158
142
  }
159
143
 
160
- function getPolylineRoute(result: number[], startPoint: IPointData, endPoint: IPointData, startPort: number, endPort: number, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number): number {
161
- const startExit: number[] = [], endExit: number[] = []
162
- getPolylineExit(startPoint, startBounds, startPort, clearance, startExit)
163
- getPolylineExit(endPoint, endBounds, endPort, clearance, endExit)
164
-
165
- const sx = startExit[0], sy = startExit[1], ex = endExit[0], ey = endExit[1]
166
- const startAxis = startPort >> 1, endAxis = endPort >> 1
167
- const startSign = startPort & 1 ? 1 : -1, endSign = endPort & 1 ? 1 : -1
144
+ function getPolylineRoute(result: number[], startPoint: IPointData, endPoint: IPointData, startPort: number, endPort: number | undefined, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number, strictPorts: boolean, approachPort?: number): number {
145
+ const startExit = getPolylineExit(startPoint, startBounds, startPort, clearance)
146
+ const endExit = endPort === undefined ? endPoint : getPolylineExit(endPoint, endBounds, endPort, clearance)
147
+ const { x: sx, y: sy } = startExit, { x: ex, y: ey } = endExit
168
148
  const startStub = Math.abs(sx - startPoint.x) + Math.abs(sy - startPoint.y)
169
149
  const endStub = Math.abs(ex - endPoint.x) + Math.abs(ey - endPoint.y)
170
150
 
171
151
  if (isPolylineSegmentBlocked(startPoint.x, startPoint.y, sx, sy, endBounds, clearance) ||
172
152
  isPolylineSegmentBlocked(endPoint.x, endPoint.y, ex, ey, startBounds, clearance)) return Infinity
173
153
 
174
- const midpointAxis = getPolylineMidpointAxis(startPort, endPort, sx, sy, ex, ey)
154
+ if (endPort === undefined && (isPolylinePointBlocked(ex, ey, startBounds, clearance) ||
155
+ isPolylinePointBlocked(ex, ey, endBounds, clearance))) return Infinity
175
156
 
176
- // 第一优先级:只要中点路径不受阻,就直接采用
177
- const midpointScore = getPolylineMidpointRoute(result, startPoint, endPoint, sx, sy, ex, ey, midpointAxis, startBounds, endBounds, clearance)
178
- if (midpointScore < Infinity) return midpointScore
157
+ const axis = endPort === undefined ? startPort >> 1 : getPolylineMidpointAxis(startPort, endPort, sx, sy, ex, ey)
158
+ const dx = ex - sx, dy = ey - sy
159
+ result.length = 0
160
+ result.push(startPoint.x, startPoint.y, sx, sy)
179
161
 
180
- // 第二优先级:中点路径受阻,使用网格寻路绕行
162
+ if ((!strictPorts || (isPolylineMidpointAligned(startPort, startStub, dx, dy, axis) &&
163
+ isPolylineMidpointAligned(endPort, endStub, -dx, -dy, axis))) &&
164
+ isPolylineMidpointAligned(approachPort, 1, -dx, -dy, axis)) {
165
+ const mx = (sx + ex) / 2, my = (sy + ey) / 2
166
+ if (axis === POLYLINE_X) result.push(mx, sy, mx, ey)
167
+ else result.push(sx, my, ex, my)
168
+ result.push(ex, ey, endPoint.x, endPoint.y)
169
+
170
+ // 两端的出线段可以进入各自所属元素
171
+ for (let i = 4; i < result.length - 2; i += 2) {
172
+ if (isPolylineSegmentBlocked(result[i - 2], result[i - 1], result[i], result[i + 1], startBounds, clearance) ||
173
+ isPolylineSegmentBlocked(result[i - 2], result[i - 1], result[i], result[i + 1], endBounds, clearance)) {
174
+ result.length = 4
175
+ break
176
+ }
177
+ }
178
+
179
+ // 兼顾出口与途径点重合的情况
180
+ if (result.length > 4 && isPolylineApproachAligned(result, approachPort)) return getPolylineScore(result, clearance)
181
+ result.length = 4
182
+ }
183
+
184
+ const score = findPolylineGridRoute(result, startExit, endExit, startPort, endPort, startBounds, endBounds, clearance, axis, startStub, endStub, strictPorts, approachPort)
185
+ if (score === Infinity) return score
186
+
187
+ result.push(endPoint.x, endPoint.y)
188
+ return isPolylineApproachAligned(result, approachPort) ? score + startStub + endStub : Infinity
189
+ }
190
+
191
+ function findPolylineGridRoute(result: number[], startExit: IPointData, endExit: IPointData, startPort: number, endPort: number | undefined, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number, midpointAxis: number, startStub: number, endStub: number, strictPorts: boolean, approachPort?: number): number {
192
+ const { x: sx, y: sy } = startExit, { x: ex, y: ey } = endExit
181
193
  const margin = Math.max(clearance, POLYLINE_MIN_SEGMENT) * 2
182
194
  const xs: number[] = [sx, ex, (sx + ex) / 2], ys: number[] = [sy, ey, (sy + ey) / 2]
183
195
 
184
196
  addPolylineBoundsCoordinates(xs, ys, startBounds, clearance, margin)
185
197
  addPolylineBoundsCoordinates(xs, ys, endBounds, clearance, margin)
186
198
 
187
- let minX = Math.min(...xs), maxX = Math.max(...xs)
188
- let minY = Math.min(...ys), maxY = Math.max(...ys)
189
- xs.push(minX - margin * 2, maxX + margin * 2)
190
- ys.push(minY - margin * 2, maxY + margin * 2)
199
+ xs.push(Math.min(...xs) - margin * 2, Math.max(...xs) + margin * 2)
200
+ ys.push(Math.min(...ys) - margin * 2, Math.max(...ys) + margin * 2)
191
201
 
192
202
  sortPolylineCoordinates(xs)
193
203
  sortPolylineCoordinates(ys)
@@ -207,47 +217,44 @@ function getPolylineRoute(result: number[], startPoint: IPointData, endPoint: IP
207
217
 
208
218
  const startState = startNode * 3
209
219
  distance[startState] = 0
220
+ const pending = [startState]
210
221
 
211
222
  const turnCost = Math.max(POLYLINE_MIN_SEGMENT, clearance * POLYLINE_TURN_WEIGHT)
223
+ const preferredCoordinate = midpointAxis === POLYLINE_X ? (sx + ex) / 2 : (sy + ey) / 2
212
224
  let currentState = -1, currentNode = -1, currentAxis = 0
213
225
  let currentX = 0, currentY = 0, currentDistance = 0
214
226
 
215
- const relax = (nextXIndex: number, nextYIndex: number, axis: number, sign: number): void => {
227
+ const relax = (nextXIndex: number, nextYIndex: number, axis: number): void => {
216
228
  const nextX = xs[nextXIndex], nextY = ys[nextYIndex]
217
229
  const nextNode = nextYIndex * nx + nextXIndex
230
+ const dx = nextX - currentX, dy = nextY - currentY
218
231
 
219
- if (isPolylinePointBlocked(nextX, nextY, startBounds, clearance) ||
220
- isPolylinePointBlocked(nextX, nextY, endBounds, clearance)) return
232
+ if (nextNode === startNode) return
221
233
 
222
- if (isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, startBounds, clearance) ||
223
- isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, endBounds, clearance)) return
234
+ // 接入约束独立于端口约束,放宽端口时仍禁止在途径点处折返
235
+ if (approachPort !== undefined && nextNode === endNode && !isPolylinePortAligned(approachPort, 1, -dx, -dy)) return
224
236
 
225
- if (currentState === startState) {
226
- if (axis === startAxis + 1) {
227
- if (sign !== startSign) return
228
- } else if (startStub <= POLYLINE_EPSILON) return
229
- }
237
+ if (strictPorts && ((currentState === startState && !isPolylinePortAligned(startPort, startStub, dx, dy)) ||
238
+ (endPort !== undefined && nextNode === endNode && !isPolylinePortAligned(endPort, endStub, -dx, -dy)))) return
230
239
 
231
- if (nextNode === endNode) {
232
- if (axis === endAxis + 1) {
233
- if (sign !== -endSign) return
234
- } else if (endStub <= POLYLINE_EPSILON) return
235
- }
240
+ if (isPolylinePointBlocked(nextX, nextY, startBounds, clearance) ||
241
+ isPolylinePointBlocked(nextX, nextY, endBounds, clearance) ||
242
+ isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, startBounds, clearance) ||
243
+ isPolylineSegmentBlocked(currentX, currentY, nextX, nextY, endBounds, clearance)) return
236
244
 
237
245
  const nextState = nextNode * 3 + axis
238
246
  if (visited[nextState]) return
239
247
 
240
- const segmentLength = Math.abs(nextX - currentX) + Math.abs(nextY - currentY)
241
- let cost = currentDistance + segmentLength
248
+ let cost = currentDistance + (Math.abs(dx) + Math.abs(dy))
242
249
 
243
250
  if (currentAxis && currentAxis !== axis) {
244
251
  cost += turnCost
245
252
  const turnCoordinate = currentAxis === POLYLINE_X + 1 ? currentX : currentY
246
- const preferredCoordinate = midpointAxis === POLYLINE_X ? (sx + ex) / 2 : (sy + ey) / 2
247
253
  if (Math.abs(turnCoordinate - preferredCoordinate) > POLYLINE_EPSILON) cost += turnCost * 2
248
254
  }
249
255
 
250
256
  if (cost < distance[nextState]) {
257
+ if (distance[nextState] === Infinity) pending.push(nextState)
251
258
  distance[nextState] = cost
252
259
  previous[nextState] = currentState
253
260
  }
@@ -255,22 +262,22 @@ function getPolylineRoute(result: number[], startPoint: IPointData, endPoint: IP
255
262
 
256
263
  let bestState = -1
257
264
 
258
- for (let iteration = 0; iteration < total; iteration++) {
259
- let minDistance = Infinity, state = -1
260
-
261
- for (let i = 0; i < total; i++) {
262
- if (!visited[i] && distance[i] < minDistance) {
263
- minDistance = distance[i]
264
- state = i
265
+ while (pending.length) {
266
+ let index = 0, state = pending[0]
267
+ for (let i = 1; i < pending.length; i++) {
268
+ const next = pending[i]
269
+ if (distance[next] < distance[state] || (distance[next] === distance[state] && next < state)) {
270
+ index = i
271
+ state = next
265
272
  }
266
273
  }
267
274
 
268
- if (state < 0) break
269
-
275
+ pending[index] = pending[pending.length - 1]
276
+ pending.pop()
270
277
  currentState = state
271
278
  currentNode = Math.floor(state / 3)
272
279
  currentAxis = state % 3
273
- currentDistance = minDistance
280
+ currentDistance = distance[state]
274
281
  visited[state] = 1
275
282
 
276
283
  if (currentNode === endNode) {
@@ -282,10 +289,10 @@ function getPolylineRoute(result: number[], startPoint: IPointData, endPoint: IP
282
289
  currentX = xs[xIndex]
283
290
  currentY = ys[yIndex]
284
291
 
285
- if (xIndex > 0) relax(xIndex - 1, yIndex, POLYLINE_X + 1, -1)
286
- if (xIndex < nx - 1) relax(xIndex + 1, yIndex, POLYLINE_X + 1, 1)
287
- if (yIndex > 0) relax(xIndex, yIndex - 1, POLYLINE_Y + 1, -1)
288
- if (yIndex < ny - 1) relax(xIndex, yIndex + 1, POLYLINE_Y + 1, 1)
292
+ if (xIndex > 0) relax(xIndex - 1, yIndex, POLYLINE_X + 1)
293
+ if (xIndex < nx - 1) relax(xIndex + 1, yIndex, POLYLINE_X + 1)
294
+ if (yIndex > 0) relax(xIndex, yIndex - 1, POLYLINE_Y + 1)
295
+ if (yIndex < ny - 1) relax(xIndex, yIndex + 1, POLYLINE_Y + 1)
289
296
  }
290
297
 
291
298
  if (bestState < 0) return Infinity
@@ -302,72 +309,91 @@ function getPolylineRoute(result: number[], startPoint: IPointData, endPoint: IP
302
309
 
303
310
  if (state !== startState) return Infinity
304
311
 
305
- result.length = 0
306
- result.push(startPoint.x, startPoint.y, sx, sy)
307
-
308
312
  for (let i = reverse.length - 4; i >= 0; i -= 2) result.push(reverse[i], reverse[i + 1])
309
-
310
- result.push(endPoint.x, endPoint.y)
311
- return distance[bestState] + startStub + endStub
313
+ return distance[bestState]
312
314
  }
313
315
 
314
- function appendPolylinePoint(points: number[], x: number, y: number): void {
315
- const length = points.length
316
- if (length >= 2 && Math.abs(points[length - 2] - x) <= POLYLINE_EPSILON && Math.abs(points[length - 1] - y) <= POLYLINE_EPSILON) return
317
- points.push(x, y)
316
+ // 锚点或途径点被元素覆盖时,保留一条尽量少穿过元素的临时接线
317
+ function getPolylineFallback(start: IPointData, end: IPointData, port: number, startBounds?: IBoundsData, endBounds?: IBoundsData): number[] {
318
+ const { x: sx, y: sy } = start, { x: ex, y: ey } = end
319
+ if (sx === ex || sy === ey) return [sx, sy, ex, ey]
320
+
321
+ const horizontal = (port >> 1) === POLYLINE_X
322
+ const crosses = (x: number, y: number): number => {
323
+ let count = 0
324
+ if (isPolylineSegmentBlocked(sx, sy, x, y, startBounds, 0)) count++
325
+ if (isPolylineSegmentBlocked(x, y, ex, ey, startBounds, 0)) count++
326
+ if (isPolylineSegmentBlocked(sx, sy, x, y, endBounds, 0)) count++
327
+ if (isPolylineSegmentBlocked(x, y, ex, ey, endBounds, 0)) count++
328
+ return count
329
+ }
330
+ const xy = crosses(ex, sy), yx = crosses(sx, ey)
331
+ return xy < yx || (xy === yx && horizontal) ? [sx, sy, ex, sy, ex, ey] : [sx, sy, sx, ey, ex, ey]
318
332
  }
319
333
 
320
- function appendPolylineOrthogonal(points: number[], x1: number, y1: number, x2: number, y2: number, firstAxis = POLYLINE_X): void {
321
- if (Math.abs(x1 - x2) > POLYLINE_EPSILON && Math.abs(y1 - y2) > POLYLINE_EPSILON) {
322
- if (firstAxis === POLYLINE_X) {
323
- const midX = (x1 + x2) / 2
324
- appendPolylinePoint(points, midX, y1)
325
- appendPolylinePoint(points, midX, y2)
326
- } else {
327
- const midY = (y1 + y2) / 2
328
- appendPolylinePoint(points, x1, midY)
329
- appendPolylinePoint(points, x2, midY)
334
+ function getBestPolyline(startPoint: IPointData, endPoint: IPointData, startDirection: IDirection, endDirection: IDirection | undefined, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number, approachPort?: number): number[] {
335
+ const startPorts = getPolylinePorts(startDirection, startPoint, endPoint, startBounds, endDirection === undefined ? undefined : endBounds)
336
+ const endPorts = endDirection === undefined ? [undefined] : getPolylinePorts(endDirection, endPoint, startPoint, endBounds, startBounds)
337
+ let candidate: number[] = [], best: number[] = [], bestScore = Infinity
338
+
339
+ // 先保留方向并减小间距,再允许贴边转向
340
+ for (let pass = clearance ? 0 : 1; pass < 3 && !best.length; pass++) {
341
+ const gap = pass ? 0 : clearance
342
+ for (let i = 0; i < startPorts.length; i++) {
343
+ for (let j = 0; j < endPorts.length; j++) {
344
+ const score = getPolylineRoute(candidate, startPoint, endPoint, startPorts[i], endPorts[j], startBounds, endBounds, gap, pass < 2, approachPort)
345
+ if (score < bestScore) {
346
+ bestScore = score
347
+ const previous = best
348
+ best = candidate
349
+ candidate = previous
350
+ }
351
+ }
330
352
  }
331
353
  }
332
- appendPolylinePoint(points, x2, y2)
333
- }
334
-
335
- function getPolylineEditPath(result: number[], startPoint: IPointData, endPoint: IPointData, startPort: number, endPort: number, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, points: number[], clearance: number): number {
336
- if (points.length % 2) return Infinity
337
- for (let i = 0; i < points.length; i++) if (!Number.isFinite(points[i])) return Infinity
338
-
339
- const startExit: number[] = [], endExit: number[] = []
340
- getPolylineExit(startPoint, startBounds, startPort, clearance, startExit)
341
- getPolylineExit(endPoint, endBounds, endPort, clearance, endExit)
342
354
 
343
- const sx = startExit[0], sy = startExit[1], ex = endExit[0], ey = endExit[1]
355
+ if (!best.length) return getPolylineFallback(startPoint, endPoint, startPorts[0], startBounds, endBounds)
356
+ simplifyPolyline(best)
357
+ const last = best.length - 2
358
+ if (best[last] !== endPoint.x || best[last + 1] !== endPoint.y) best.push(endPoint.x, endPoint.y)
359
+ return best
360
+ }
344
361
 
345
- if (isPolylineSegmentBlocked(startPoint.x, startPoint.y, sx, sy, endBounds, clearance) ||
346
- isPolylineSegmentBlocked(endPoint.x, endPoint.y, ex, ey, startBounds, clearance)) return Infinity
362
+ function getPolylineEditSide(point: IPointData, waypoint: IPointData, direction: IDirection, bounds: IBoundsData | undefined, otherBounds: IBoundsData | undefined, clearance: number, points: number[], index: number, step: number): number[] {
363
+ if (point.x === waypoint.x && point.y === waypoint.y) return []
347
364
 
348
- result.length = 0
349
- appendPolylinePoint(result, startPoint.x, startPoint.y)
350
- appendPolylinePoint(result, sx, sy)
365
+ let approachPort: number | undefined
351
366
 
352
- let x = sx, y = sy
367
+ // 跳过重合途径点,根据后续线段确定接入方向
368
+ for (let i = index + step; i >= 0 && i < points.length; i += step) {
369
+ const dx = points[i] - waypoint.x, dy = points[i + 1] - waypoint.y
370
+ if (Math.abs(dx) <= POLYLINE_EPSILON && Math.abs(dy) <= POLYLINE_EPSILON) continue
353
371
 
354
- for (let i = 0; i < points.length; i += 2) {
355
- const nx = points[i], ny = points[i + 1]
356
- appendPolylineOrthogonal(result, x, y, nx, ny, i === 0 && clearance === 0 ? startPort >> 1 : POLYLINE_X)
357
- x = nx
358
- y = ny
372
+ if (Math.abs(dy) <= POLYLINE_EPSILON) approachPort = dx > 0 ? 0 : 1
373
+ else if (Math.abs(dx) <= POLYLINE_EPSILON) approachPort = dy > 0 ? 2 : 3
374
+ break
359
375
  }
360
376
 
361
- const endAxis = endPort >> 1
377
+ const side = getBestPolyline(point, waypoint, direction, undefined, bounds, otherBounds, clearance, approachPort)
378
+ side.length -= 2
379
+ return side
380
+ }
362
381
 
363
- if (Math.abs(x - ex) > POLYLINE_EPSILON && Math.abs(y - ey) > POLYLINE_EPSILON) {
364
- if (endAxis === POLYLINE_X) appendPolylinePoint(result, x, ey)
365
- else appendPolylinePoint(result, ex, y)
366
- }
382
+ function drawPolylineEditPath(path: number[], startPoint: IPointData, endPoint: IPointData, startDirection: IDirection, endDirection: IDirection, startBounds: IBoundsData | undefined, endBounds: IBoundsData | undefined, clearance: number, points: number[]): boolean {
383
+ const length = points.length, last = length - 2
384
+ if (length % 2) return false
385
+ for (let i = 0; i < length; i++) if (!Number.isFinite(points[i])) return false
367
386
 
368
- appendPolylinePoint(result, ex, ey)
369
- appendPolylinePoint(result, endPoint.x, endPoint.y)
370
- return getPolylineScore(result, clearance)
387
+ const start = getPolylineEditSide(startPoint, { x: points[0], y: points[1] }, startDirection, startBounds, endBounds, clearance, points, 0, 2)
388
+ const end = getPolylineEditSide(endPoint, { x: points[last], y: points[last + 1] }, endDirection, endBounds, startBounds, clearance, points, last, -2)
389
+
390
+ // 只拼接两端生成的接线,中间顶点按原坐标写入
391
+ const first = start.length ? start : points
392
+ moveTo(path, first[0], first[1])
393
+ for (let i = 2; i < start.length; i += 2) lineTo(path, start[i], start[i + 1])
394
+ for (let i = start.length ? 0 : 2; i < length; i += 2) lineTo(path, points[i], points[i + 1])
395
+ for (let i = end.length - 2; i >= 0; i -= 2) lineTo(path, end[i], end[i + 1])
396
+ return true
371
397
  }
372
398
 
373
399
  function getPolylineScore(points: number[], clearance: number): number {
@@ -421,35 +447,9 @@ export function drawPolyline(path: number[], startPoint: IPointData, endPoint: I
421
447
  if (endBounds && (!Number.isFinite(endBounds.x) || !Number.isFinite(endBounds.y) || !Number.isFinite(endBounds.width) || !Number.isFinite(endBounds.height) || endBounds.width < 0 || endBounds.height < 0)) return false
422
448
 
423
449
  clearance = Number.isFinite(clearance) ? Math.max(0, clearance) : 8
450
+ if (points?.length) return drawPolylineEditPath(path, startPoint, endPoint, startDirection, endDirection, startBounds, endBounds, clearance, points)
424
451
 
425
- const startPorts: number[] = [], endPorts: number[] = []
426
- const startCount = getPolylinePorts(startDirection, startPoint, endPoint, startBounds, endBounds, startPorts)
427
- const endCount = getPolylinePorts(endDirection, endPoint, startPoint, endBounds, startBounds, endPorts)
428
- const candidate: number[] = [], best: number[] = []
429
-
430
- let bestScore = Infinity
431
-
432
- for (let pass = 0; pass < 2 && bestScore === Infinity; pass++) {
433
- const currentClearance = pass === 0 ? clearance : 0
434
-
435
- for (let i = 0; i < startCount; i++) {
436
- for (let j = 0; j < endCount; j++) {
437
- const score = points !== undefined
438
- ? getPolylineEditPath(candidate, startPoint, endPoint, startPorts[i], endPorts[j], startBounds, endBounds, points, currentClearance)
439
- : getPolylineRoute(candidate, startPoint, endPoint, startPorts[i], endPorts[j], startBounds, endBounds, currentClearance)
440
-
441
- if (score < bestScore) {
442
- bestScore = score
443
- best.length = 0
444
- for (let k = 0; k < candidate.length; k++) best.push(candidate[k])
445
- }
446
- }
447
- }
448
- }
449
-
450
- if (!best.length) return false
451
- if (points === undefined) simplifyPolyline(best)
452
-
452
+ const best = getBestPolyline(startPoint, endPoint, startDirection, endDirection, startBounds, endBounds, clearance)
453
453
  moveTo(path, best[0], best[1])
454
454
  for (let i = 2; i < best.length; i += 2) lineTo(path, best[i], best[i + 1])
455
455
  return true