@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.
- package/dist/linker.cjs +166 -152
- package/dist/linker.esm.js +166 -152
- package/dist/linker.esm.min.js +1 -1
- package/dist/linker.esm.min.js.map +1 -1
- package/dist/linker.js +162 -152
- package/dist/linker.min.cjs +1 -1
- package/dist/linker.min.cjs.map +1 -1
- package/dist/linker.min.js +1 -1
- package/dist/linker.min.js.map +1 -1
- package/package.json +1 -1
- package/src/helper/drawPolyline.ts +177 -177
|
@@ -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
|
|
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
|
|
35
|
-
const
|
|
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
|
|
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
|
|
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
|
|
65
|
-
|
|
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
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
|
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
|
|
162
|
-
getPolylineExit(
|
|
163
|
-
|
|
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
|
-
|
|
154
|
+
if (endPort === undefined && (isPolylinePointBlocked(ex, ey, startBounds, clearance) ||
|
|
155
|
+
isPolylinePointBlocked(ex, ey, endBounds, clearance))) return Infinity
|
|
175
156
|
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
|
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 (
|
|
220
|
-
isPolylinePointBlocked(nextX, nextY, endBounds, clearance)) return
|
|
232
|
+
if (nextNode === startNode) return
|
|
221
233
|
|
|
222
|
-
|
|
223
|
-
|
|
234
|
+
// 接入约束独立于端口约束,放宽端口时仍禁止在途径点处折返
|
|
235
|
+
if (approachPort !== undefined && nextNode === endNode && !isPolylinePortAligned(approachPort, 1, -dx, -dy)) return
|
|
224
236
|
|
|
225
|
-
if (currentState === startState)
|
|
226
|
-
|
|
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 (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
let
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
state =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
286
|
-
if (xIndex < nx - 1) relax(xIndex + 1, yIndex, POLYLINE_X + 1
|
|
287
|
-
if (yIndex > 0) relax(xIndex, yIndex - 1, POLYLINE_Y + 1
|
|
288
|
-
if (yIndex < ny - 1) relax(xIndex, yIndex + 1, POLYLINE_Y + 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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
|
|
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
|
-
|
|
349
|
-
appendPolylinePoint(result, startPoint.x, startPoint.y)
|
|
350
|
-
appendPolylinePoint(result, sx, sy)
|
|
365
|
+
let approachPort: number | undefined
|
|
351
366
|
|
|
352
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
|
377
|
+
const side = getBestPolyline(point, waypoint, direction, undefined, bounds, otherBounds, clearance, approachPort)
|
|
378
|
+
side.length -= 2
|
|
379
|
+
return side
|
|
380
|
+
}
|
|
362
381
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
|
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
|