vehicle-path2 4.0.0 → 4.0.2

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/README.md CHANGED
@@ -1,289 +1,289 @@
1
- # vehicle-path2
2
-
3
- Library untuk simulasi pergerakan kendaraan multi-axle sepanjang jalur yang terdiri dari Lines dan Curves.
4
-
5
- ## Instalasi
6
-
7
- ```bash
8
- npm install vehicle-path2
9
- ```
10
-
11
- ## Konsep Dasar
12
-
13
- - **Line** — segmen garis lurus antara dua titik. Kendaraan bergerak di atas line.
14
- - **Curve** — koneksi antara dua line. Berbentuk kurva bezier, menghubungkan ujung satu line ke awal line berikutnya.
15
- - **Vehicle** — kendaraan dengan N axle yang ditempatkan di atas sebuah line. Library tidak menyimpan daftar vehicle — itu tanggung jawab client.
16
-
17
- Titik acuan kendaraan adalah **axle paling belakang** (`axles[N-1]`). Semua axle lainnya dihitung ke depan berdasarkan `axleSpacings`.
18
-
19
- ---
20
-
21
- ## Setup
22
-
23
- ```typescript
24
- import { PathEngine } from 'vehicle-path2/core'
25
-
26
- const engine = new PathEngine({
27
- maxWheelbase: 100, // batas maksimum total panjang kendaraan
28
- tangentMode: 'proportional-40' // mode kurva bezier
29
- })
30
- ```
31
-
32
- ---
33
-
34
- ## Manajemen Scene
35
-
36
- ### Lines
37
-
38
- ```typescript
39
- // Tambah line
40
- engine.addLine({ id: 'L1', start: { x: 0, y: 0 }, end: { x: 400, y: 0 } })
41
- // → false jika ID sudah ada
42
-
43
- // Update posisi titik
44
- engine.updateLine('L1', { end: { x: 500, y: 0 } })
45
- engine.updateLineEndpoint('L1', 'start', { x: 10, y: 0 })
46
-
47
- // Rename — cascade: semua curve yang referensi 'L1' otomatis diupdate ke 'L1-new'
48
- engine.renameLine('L1', 'L1-new')
49
- // → { success: true } atau { success: false, error: '...' }
50
-
51
- // Hapus — cascade: semua curve yang terhubung ke line ini ikut dihapus
52
- engine.removeLine('L1')
53
-
54
- // Baca semua lines
55
- engine.lines // → Line[]
56
- ```
57
-
58
- > **Konsekuensi `removeLine`:** Semua curve yang `fromLineId` atau `toLineId`-nya adalah line tersebut akan otomatis ikut dihapus.
59
-
60
- > **Konsekuensi `renameLine`:** Semua curve yang `fromLineId` atau `toLineId`-nya adalah ID lama otomatis diupdate ke ID baru. Graph di-rebuild secara otomatis.
61
-
62
- ---
63
-
64
- ### Curves
65
-
66
- Curve menghubungkan **ujung satu line** ke **awal line lain**. Tidak bisa berdiri sendiri — harus selalu mereferensi dua line yang valid.
67
-
68
- ```typescript
69
- import type { Curve } from 'vehicle-path2/core'
70
-
71
- // Tambah curve
72
- engine.addCurve({
73
- fromLineId: 'L1',
74
- toLineId: 'L2',
75
- fromOffset: 380, // posisi di L1 (px dari start)
76
- fromIsPercentage: false,
77
- toOffset: 20, // posisi di L2 (px dari start)
78
- toIsPercentage: false,
79
- } as Curve)
80
-
81
- // Atau dengan offset persentase (0–1)
82
- engine.addCurve({
83
- fromLineId: 'L1',
84
- toLineId: 'L2',
85
- fromOffset: 0.95,
86
- fromIsPercentage: true,
87
- toOffset: 0.05,
88
- toIsPercentage: true,
89
- } as Curve)
90
-
91
- // Update curve berdasarkan index
92
- engine.updateCurve(0, { fromOffset: 0.9 })
93
-
94
- // Hapus curve berdasarkan index
95
- engine.removeCurve(0)
96
-
97
- // Baca semua curves
98
- engine.getCurves() // → Curve[]
99
- ```
100
-
101
- > **Catatan:** Curve diidentifikasi via **array index** (bukan named ID). Gunakan `getCurves()` lalu cari index yang sesuai sebelum update/delete.
102
-
103
- > **Konsekuensi `removeLine`:** Curve yang terhubung ke line yang dihapus otomatis ikut terhapus, sehingga index curve-curve lainnya bisa bergeser.
104
-
105
- ---
106
-
107
- ### Muat Scene Sekaligus
108
-
109
- ```typescript
110
- // Replace seluruh scene secara atomik
111
- engine.setScene(lines, curves)
112
- ```
113
-
114
- ---
115
-
116
- ## Vehicle
117
-
118
- Library menyediakan tipe dasar `VehicleDefinition`. Client bebas extend dengan field tambahan (id, name, color, dsb).
119
-
120
- ```typescript
121
- import type { VehicleDefinition } from 'vehicle-path2/core'
122
-
123
- // Definisi minimal
124
- const def: VehicleDefinition = { axleSpacings: [40] } // 2 axle, jarak 40px
125
-
126
- // Client extend sesuai kebutuhan
127
- interface MyVehicle extends VehicleDefinition {
128
- id: string
129
- name: string
130
- }
131
-
132
- const truck: MyVehicle = { id: 'v1', name: 'Truck A', axleSpacings: [40, 40] } // 3 axle
133
- ```
134
-
135
- > `axleSpacings[i]` = jarak arc-length antara `axles[i]` dan `axles[i+1]`. Array harus memiliki minimal 1 entry.
136
-
137
- ---
138
-
139
- ## Pergerakan Kendaraan
140
-
141
- Tiga method berikut digunakan secara berurutan:
142
-
143
- ### 1. `initializeVehicle` — Tempatkan kendaraan
144
-
145
- Hitung posisi awal semua axle di atas sebuah line.
146
-
147
- ```typescript
148
- const state = engine.initializeVehicle(
149
- 'L1', // lineId: line tempat kendaraan ditempatkan
150
- 0, // rearOffset: jarak dari start line ke axle paling belakang (px)
151
- truck // VehicleDefinition (atau object yang extends-nya)
152
- )
153
-
154
- // state → VehiclePathState | null (null jika lineId tidak ditemukan)
155
- // state.axles[0] = axle terdepan
156
- // state.axles[N-1] = axle paling belakang (titik acuan)
157
- // state.axleSpacings = [40, 40]
158
- ```
159
-
160
- ### 2. `preparePath` — Tentukan tujuan
161
-
162
- Jalankan Dijkstra untuk mencari rute dari posisi saat ini ke tujuan. Dipanggil **sekali** sebelum animasi dimulai.
163
-
164
- ```typescript
165
- const execution = engine.preparePath(
166
- state, // posisi vehicle sekarang (dari initializeVehicle atau tick sebelumnya)
167
- 'L3', // targetLineId: line tujuan
168
- 0.5, // targetOffset: posisi di line tujuan
169
- true // isPercentage: true = 50% dari panjang line, false = nilai absolut (px)
170
- )
171
-
172
- // execution → PathExecution | null (null jika tidak ada rute)
173
- ```
174
-
175
- ### 3. `moveVehicle` — Jalankan per tick
176
-
177
- Dipanggil setiap frame di animation loop. Mengembalikan posisi baru semua axle.
178
-
179
- ```typescript
180
- const result = engine.moveVehicle(
181
- state, // posisi vehicle sekarang
182
- execution, // rencana rute (dari preparePath)
183
- speed * deltaTime // jarak yang ditempuh di frame ini (px)
184
- )
185
-
186
- state = result.state // posisi baru → simpan untuk tick berikutnya
187
- execution = result.execution // progress terbaru → simpan untuk tick berikutnya
188
-
189
- if (result.arrived) {
190
- // Kendaraan sudah sampai tujuan
191
- }
192
- ```
193
-
194
- ---
195
-
196
- ### Contoh Lengkap: Animation Loop
197
-
198
- ```typescript
199
- import { PathEngine } from 'vehicle-path2/core'
200
- import type { VehiclePathState, PathExecution } from 'vehicle-path2/core'
201
-
202
- const engine = new PathEngine({ maxWheelbase: 100, tangentMode: 'proportional-40' })
203
-
204
- engine.setScene(
205
- [
206
- { id: 'L1', start: { x: 0, y: 0 }, end: { x: 400, y: 0 } },
207
- { id: 'L2', start: { x: 400, y: 0 }, end: { x: 400, y: 300 } },
208
- ],
209
- [
210
- { fromLineId: 'L1', toLineId: 'L2', fromOffset: 1.0, fromIsPercentage: true, toOffset: 0.0, toIsPercentage: true }
211
- ]
212
- )
213
-
214
- // Client mengelola data vehicle sendiri
215
- const myVehicle = { id: 'v1', name: 'Truck', axleSpacings: [40] }
216
-
217
- // 1. Tempatkan di L1, axle belakang di posisi 0
218
- let state: VehiclePathState = engine.initializeVehicle('L1', 0, myVehicle)!
219
-
220
- // 2. Tentukan tujuan: ujung L2
221
- let execution: PathExecution = engine.preparePath(state, 'L2', 1.0, true)!
222
-
223
- const speed = 80 // px/detik
224
- let lastTime = performance.now()
225
-
226
- function animate() {
227
- const now = performance.now()
228
- const deltaTime = (now - lastTime) / 1000 // dalam detik
229
- lastTime = now
230
-
231
- // 3. Gerakkan vehicle setiap frame
232
- const result = engine.moveVehicle(state, execution, speed * deltaTime)
233
- state = result.state
234
- execution = result.execution
235
-
236
- // Render semua axle
237
- for (const axle of state.axles) {
238
- drawCircle(axle.position.x, axle.position.y)
239
- }
240
-
241
- if (!result.arrived) {
242
- requestAnimationFrame(animate)
243
- }
244
- }
245
-
246
- requestAnimationFrame(animate)
247
- ```
248
-
249
- ---
250
-
251
- ## Serialisasi Scene
252
-
253
- Hanya lines dan curves yang di-serialize oleh library. **Vehicle tidak termasuk** — persistensi vehicle adalah tanggung jawab client.
254
-
255
- ```typescript
256
- import { serializeScene, deserializeScene } from 'vehicle-path2/core'
257
-
258
- // Simpan scene ke JSON string
259
- const json = serializeScene(engine.lines, engine.getCurves())
260
-
261
- // Muat kembali
262
- const snapshot = deserializeScene(json)
263
- engine.setScene(snapshot.lines, snapshot.curves)
264
- ```
265
-
266
- ---
267
-
268
- ## Geometry Utilities
269
-
270
- ```typescript
271
- import { projectPointOnLine, getValidRearOffsetRange, computeMinLineLength } from 'vehicle-path2/core'
272
-
273
- // Proyeksikan mouse/pointer ke garis — berguna untuk hit detection
274
- const { offset, distance } = projectPointOnLine({ x: 150, y: 10 }, line)
275
- // offset = jarak dari start line ke titik proyeksi (px)
276
- // distance = jarak tegak lurus dari point ke garis (px)
277
-
278
- // Hitung range offset yang valid untuk axle belakang agar semua axle muat di line
279
- const [min, max] = getValidRearOffsetRange(line, myVehicle.axleSpacings)
280
-
281
- // Hitung panjang minimum sebuah line agar semua curve offset-nya tidak keluar batas
282
- const minLen = computeMinLineLength('L1', engine.getCurves())
283
- ```
284
-
285
- ---
286
-
287
- ## License
288
-
289
- MIT
1
+ # vehicle-path2
2
+
3
+ Library untuk simulasi pergerakan kendaraan multi-axle sepanjang jalur yang terdiri dari Lines dan Curves.
4
+
5
+ ## Instalasi
6
+
7
+ ```bash
8
+ npm install vehicle-path2
9
+ ```
10
+
11
+ ## Konsep Dasar
12
+
13
+ - **Line** — segmen garis lurus antara dua titik. Kendaraan bergerak di atas line.
14
+ - **Curve** — koneksi antara dua line. Berbentuk kurva bezier, menghubungkan ujung satu line ke awal line berikutnya.
15
+ - **Vehicle** — kendaraan dengan N axle yang ditempatkan di atas sebuah line. Library tidak menyimpan daftar vehicle — itu tanggung jawab client.
16
+
17
+ Titik acuan kendaraan adalah **axle paling belakang** (`axles[N-1]`). Semua axle lainnya dihitung ke depan berdasarkan `axleSpacings`.
18
+
19
+ ---
20
+
21
+ ## Setup
22
+
23
+ ```typescript
24
+ import { PathEngine } from 'vehicle-path2/core'
25
+
26
+ const engine = new PathEngine({
27
+ maxWheelbase: 100, // batas maksimum total panjang kendaraan
28
+ tangentMode: 'proportional-40' // mode kurva bezier
29
+ })
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Manajemen Scene
35
+
36
+ ### Lines
37
+
38
+ ```typescript
39
+ // Tambah line
40
+ engine.addLine({ id: 'L1', start: { x: 0, y: 0 }, end: { x: 400, y: 0 } })
41
+ // → false jika ID sudah ada
42
+
43
+ // Update posisi titik
44
+ engine.updateLine('L1', { end: { x: 500, y: 0 } })
45
+ engine.updateLineEndpoint('L1', 'start', { x: 10, y: 0 })
46
+
47
+ // Rename — cascade: semua curve yang referensi 'L1' otomatis diupdate ke 'L1-new'
48
+ engine.renameLine('L1', 'L1-new')
49
+ // → { success: true } atau { success: false, error: '...' }
50
+
51
+ // Hapus — cascade: semua curve yang terhubung ke line ini ikut dihapus
52
+ engine.removeLine('L1')
53
+
54
+ // Baca semua lines
55
+ engine.lines // → Line[]
56
+ ```
57
+
58
+ > **Konsekuensi `removeLine`:** Semua curve yang `fromLineId` atau `toLineId`-nya adalah line tersebut akan otomatis ikut dihapus.
59
+
60
+ > **Konsekuensi `renameLine`:** Semua curve yang `fromLineId` atau `toLineId`-nya adalah ID lama otomatis diupdate ke ID baru. Graph di-rebuild secara otomatis.
61
+
62
+ ---
63
+
64
+ ### Curves
65
+
66
+ Curve menghubungkan **ujung satu line** ke **awal line lain**. Tidak bisa berdiri sendiri — harus selalu mereferensi dua line yang valid.
67
+
68
+ ```typescript
69
+ import type { Curve } from 'vehicle-path2/core'
70
+
71
+ // Tambah curve
72
+ engine.addCurve({
73
+ fromLineId: 'L1',
74
+ toLineId: 'L2',
75
+ fromOffset: 380, // posisi di L1 (px dari start)
76
+ fromIsPercentage: false,
77
+ toOffset: 20, // posisi di L2 (px dari start)
78
+ toIsPercentage: false,
79
+ } as Curve)
80
+
81
+ // Atau dengan offset persentase (0–1)
82
+ engine.addCurve({
83
+ fromLineId: 'L1',
84
+ toLineId: 'L2',
85
+ fromOffset: 0.95,
86
+ fromIsPercentage: true,
87
+ toOffset: 0.05,
88
+ toIsPercentage: true,
89
+ } as Curve)
90
+
91
+ // Update curve berdasarkan index
92
+ engine.updateCurve(0, { fromOffset: 0.9 })
93
+
94
+ // Hapus curve berdasarkan index
95
+ engine.removeCurve(0)
96
+
97
+ // Baca semua curves
98
+ engine.getCurves() // → Curve[]
99
+ ```
100
+
101
+ > **Catatan:** Curve diidentifikasi via **array index** (bukan named ID). Gunakan `getCurves()` lalu cari index yang sesuai sebelum update/delete.
102
+
103
+ > **Konsekuensi `removeLine`:** Curve yang terhubung ke line yang dihapus otomatis ikut terhapus, sehingga index curve-curve lainnya bisa bergeser.
104
+
105
+ ---
106
+
107
+ ### Muat Scene Sekaligus
108
+
109
+ ```typescript
110
+ // Replace seluruh scene secara atomik
111
+ engine.setScene(lines, curves)
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Vehicle
117
+
118
+ Library menyediakan tipe dasar `VehicleDefinition`. Client bebas extend dengan field tambahan (id, name, color, dsb).
119
+
120
+ ```typescript
121
+ import type { VehicleDefinition } from 'vehicle-path2/core'
122
+
123
+ // Definisi minimal
124
+ const def: VehicleDefinition = { axleSpacings: [40] } // 2 axle, jarak 40px
125
+
126
+ // Client extend sesuai kebutuhan
127
+ interface MyVehicle extends VehicleDefinition {
128
+ id: string
129
+ name: string
130
+ }
131
+
132
+ const truck: MyVehicle = { id: 'v1', name: 'Truck A', axleSpacings: [40, 40] } // 3 axle
133
+ ```
134
+
135
+ > `axleSpacings[i]` = jarak arc-length antara `axles[i]` dan `axles[i+1]`. Array harus memiliki minimal 1 entry.
136
+
137
+ ---
138
+
139
+ ## Pergerakan Kendaraan
140
+
141
+ Tiga method berikut digunakan secara berurutan:
142
+
143
+ ### 1. `initializeVehicle` — Tempatkan kendaraan
144
+
145
+ Hitung posisi awal semua axle di atas sebuah line.
146
+
147
+ ```typescript
148
+ const state = engine.initializeVehicle(
149
+ 'L1', // lineId: line tempat kendaraan ditempatkan
150
+ 0, // rearOffset: jarak dari start line ke axle paling belakang (px)
151
+ truck // VehicleDefinition (atau object yang extends-nya)
152
+ )
153
+
154
+ // state → VehiclePathState | null (null jika lineId tidak ditemukan)
155
+ // state.axles[0] = axle terdepan
156
+ // state.axles[N-1] = axle paling belakang (titik acuan)
157
+ // state.axleSpacings = [40, 40]
158
+ ```
159
+
160
+ ### 2. `preparePath` — Tentukan tujuan
161
+
162
+ Jalankan Dijkstra untuk mencari rute dari posisi saat ini ke tujuan. Dipanggil **sekali** sebelum animasi dimulai.
163
+
164
+ ```typescript
165
+ const execution = engine.preparePath(
166
+ state, // posisi vehicle sekarang (dari initializeVehicle atau tick sebelumnya)
167
+ 'L3', // targetLineId: line tujuan
168
+ 0.5, // targetOffset: posisi di line tujuan
169
+ true // isPercentage: true = 50% dari panjang line, false = nilai absolut (px)
170
+ )
171
+
172
+ // execution → PathExecution | null (null jika tidak ada rute)
173
+ ```
174
+
175
+ ### 3. `moveVehicle` — Jalankan per tick
176
+
177
+ Dipanggil setiap frame di animation loop. Mengembalikan posisi baru semua axle.
178
+
179
+ ```typescript
180
+ const result = engine.moveVehicle(
181
+ state, // posisi vehicle sekarang
182
+ execution, // rencana rute (dari preparePath)
183
+ speed * deltaTime // jarak yang ditempuh di frame ini (px)
184
+ )
185
+
186
+ state = result.state // posisi baru → simpan untuk tick berikutnya
187
+ execution = result.execution // progress terbaru → simpan untuk tick berikutnya
188
+
189
+ if (result.arrived) {
190
+ // Kendaraan sudah sampai tujuan
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ ### Contoh Lengkap: Animation Loop
197
+
198
+ ```typescript
199
+ import { PathEngine } from 'vehicle-path2/core'
200
+ import type { VehiclePathState, PathExecution } from 'vehicle-path2/core'
201
+
202
+ const engine = new PathEngine({ maxWheelbase: 100, tangentMode: 'proportional-40' })
203
+
204
+ engine.setScene(
205
+ [
206
+ { id: 'L1', start: { x: 0, y: 0 }, end: { x: 400, y: 0 } },
207
+ { id: 'L2', start: { x: 400, y: 0 }, end: { x: 400, y: 300 } },
208
+ ],
209
+ [
210
+ { fromLineId: 'L1', toLineId: 'L2', fromOffset: 1.0, fromIsPercentage: true, toOffset: 0.0, toIsPercentage: true }
211
+ ]
212
+ )
213
+
214
+ // Client mengelola data vehicle sendiri
215
+ const myVehicle = { id: 'v1', name: 'Truck', axleSpacings: [40] }
216
+
217
+ // 1. Tempatkan di L1, axle belakang di posisi 0
218
+ let state: VehiclePathState = engine.initializeVehicle('L1', 0, myVehicle)!
219
+
220
+ // 2. Tentukan tujuan: ujung L2
221
+ let execution: PathExecution = engine.preparePath(state, 'L2', 1.0, true)!
222
+
223
+ const speed = 80 // px/detik
224
+ let lastTime = performance.now()
225
+
226
+ function animate() {
227
+ const now = performance.now()
228
+ const deltaTime = (now - lastTime) / 1000 // dalam detik
229
+ lastTime = now
230
+
231
+ // 3. Gerakkan vehicle setiap frame
232
+ const result = engine.moveVehicle(state, execution, speed * deltaTime)
233
+ state = result.state
234
+ execution = result.execution
235
+
236
+ // Render semua axle
237
+ for (const axle of state.axles) {
238
+ drawCircle(axle.position.x, axle.position.y)
239
+ }
240
+
241
+ if (!result.arrived) {
242
+ requestAnimationFrame(animate)
243
+ }
244
+ }
245
+
246
+ requestAnimationFrame(animate)
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Serialisasi Scene
252
+
253
+ Hanya lines dan curves yang di-serialize oleh library. **Vehicle tidak termasuk** — persistensi vehicle adalah tanggung jawab client.
254
+
255
+ ```typescript
256
+ import { serializeScene, deserializeScene } from 'vehicle-path2/core'
257
+
258
+ // Simpan scene ke JSON string
259
+ const json = serializeScene(engine.lines, engine.getCurves())
260
+
261
+ // Muat kembali
262
+ const snapshot = deserializeScene(json)
263
+ engine.setScene(snapshot.lines, snapshot.curves)
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Geometry Utilities
269
+
270
+ ```typescript
271
+ import { projectPointOnLine, getValidRearOffsetRange, computeMinLineLength } from 'vehicle-path2/core'
272
+
273
+ // Proyeksikan mouse/pointer ke garis — berguna untuk hit detection
274
+ const { offset, distance } = projectPointOnLine({ x: 150, y: 10 }, line)
275
+ // offset = jarak dari start line ke titik proyeksi (px)
276
+ // distance = jarak tegak lurus dari point ke garis (px)
277
+
278
+ // Hitung range offset yang valid untuk axle belakang agar semua axle muat di line
279
+ const [min, max] = getValidRearOffsetRange(line, myVehicle.axleSpacings)
280
+
281
+ // Hitung panjang minimum sebuah line agar semua curve offset-nya tidak keluar batas
282
+ const minLen = computeMinLineLength('L1', engine.getCurves())
283
+ ```
284
+
285
+ ---
286
+
287
+ ## License
288
+
289
+ MIT
package/dist/core.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./index-DQbf63_R.cjs");function P(n,e){const a={lines:n,curves:e.map(t=>({id:t.id,fromLineId:t.fromLineId,toLineId:t.toLineId,fromOffset:t.fromOffset,fromIsPercentage:t.fromIsPercentage??!1,toOffset:t.toOffset,toIsPercentage:t.toIsPercentage??!1}))};return JSON.stringify(a,null,2)}function O(n){let e;try{e=JSON.parse(n)}catch{throw new Error("deserializeScene: invalid JSON")}if(!e||typeof e!="object"||Array.isArray(e))throw new Error("deserializeScene: expected a JSON object");const a=e;if(!Array.isArray(a.lines))throw new Error('deserializeScene: missing "lines"');if(!Array.isArray(a.curves))throw new Error('deserializeScene: missing "curves"');return{lines:a.lines,curves:a.curves}}function h(n){const e=n.axleExecutions[n.axleExecutions.length-1],a=i.getCumulativeArcLength(n.path,e.segmentIndex,e.segmentDistance);return Math.max(0,n.path.totalDistance-a)}function d(n){const e=n.axleExecutions[0],a=i.getCumulativeArcLength(n.path,e.segmentIndex,e.segmentDistance);let t=0;for(let r=0;r<n.path.segments.length;r++){const o=n.path.segments[r];if(r>=e.segmentIndex&&o.type==="curve")return Math.max(0,t-a);t+=o.length}return null}function p(n,e,a){let t=a.maxSpeed;const r=Math.sqrt(2*a.deceleration*Math.max(0,n));if(t=Math.min(t,r),e!==null){const o=Math.sqrt(a.minCurveSpeed**2+2*a.deceleration*e);t=Math.min(t,o)}return Math.max(0,t)}function x(n,e,a,t,r){return n<e?Math.min(e,n+a*r):n>e?Math.max(e,n-t*r):n}function S(n,e,a,t,r,o){const c=h(e),l=d(e),f=p(c,l,t),g=x(a.currentSpeed,f,t.acceleration,t.deceleration,r),u=g*r,v=n.axles.map(s=>({lineId:s.lineId,position:s.position,absoluteOffset:s.offset})),L=e.axleExecutions.map(s=>({currentSegmentIndex:s.segmentIndex,segmentDistance:s.segmentDistance})),m=i.moveVehicle(v,L,e.path,u,o,e.curveDataMap);return{state:{axles:m.axles.map(s=>({lineId:s.lineId,offset:s.absoluteOffset,position:s.position})),axleSpacings:n.axleSpacings},execution:{...e,axleExecutions:m.axleExecutions.map(s=>({segmentIndex:s.currentSegmentIndex,segmentDistance:s.segmentDistance}))},accelState:{currentSpeed:g},arrived:m.arrived}}function A(n,e){const a=e.end.x-e.start.x,t=e.end.y-e.start.y,r=a*a+t*t;if(r===0)return{offset:0,distance:Math.sqrt((n.x-e.start.x)**2+(n.y-e.start.y)**2)};const o=Math.max(0,Math.min(1,((n.x-e.start.x)*a+(n.y-e.start.y)*t)/r)),c=e.start.x+o*a,l=e.start.y+o*t,f=Math.sqrt((n.x-c)**2+(n.y-l)**2);return{offset:o*Math.sqrt(r),distance:f}}function I(n,e){const a=i.getLineLength(n),t=e.reduce((o,c)=>o+c,0);return[0,Math.max(0,a-t)]}function M(n,e){let a=0;for(const t of e)t.fromLineId===n&&!t.fromIsPercentage&&t.fromOffset!==void 0&&(a=Math.max(a,t.fromOffset)),t.toLineId===n&&!t.toIsPercentage&&t.toOffset!==void 0&&(a=Math.max(a,t.toOffset));return a}exports.PathEngine=i.PathEngine;exports.arcLengthToSegmentPosition=i.arcLengthToSegmentPosition;exports.buildArcLengthTable=i.buildArcLengthTable;exports.buildGraph=i.buildGraph;exports.calculateBezierArcLength=i.calculateBezierArcLength;exports.calculateFrontAxlePosition=i.calculateFrontAxlePosition;exports.calculateInitialAxlePositions=i.calculateInitialAxlePositions;exports.calculatePositionOnCurve=i.calculatePositionOnCurve;exports.calculatePositionOnLine=i.calculatePositionOnLine;exports.calculateTangentLength=i.calculateTangentLength;exports.createBezierCurve=i.createBezierCurve;exports.createInitialMovementState=i.createInitialMovementState;exports.distance=i.distance;exports.distanceToT=i.distanceToT;exports.findPath=i.findPath;exports.getArcLength=i.getArcLength;exports.getCumulativeArcLength=i.getCumulativeArcLength;exports.getLineLength=i.getLineLength;exports.getPointOnBezier=i.getPointOnBezier;exports.getPointOnLine=i.getPointOnLine;exports.getPointOnLineByOffset=i.getPointOnLineByOffset;exports.getPositionFromOffset=i.getPositionFromOffset;exports.handleArrival=i.handleArrival;exports.initializeAllVehicles=i.initializeAllVehicles;exports.initializeMovingVehicle=i.initializeMovingVehicle;exports.isPointNearPoint=i.isPointNearPoint;exports.moveVehicle=i.moveVehicle;exports.normalize=i.normalize;exports.prepareCommandPath=i.prepareCommandPath;exports.resolveFromLineOffset=i.resolveFromLineOffset;exports.resolveToLineOffset=i.resolveToLineOffset;exports.updateAxlePosition=i.updateAxlePosition;exports.approachSpeed=x;exports.computeDistToNextCurve=d;exports.computeMinLineLength=M;exports.computeRemainingToArrival=h;exports.computeTargetSpeed=p;exports.deserializeScene=O;exports.getValidRearOffsetRange=I;exports.moveVehicleWithAcceleration=S;exports.projectPointOnLine=A;exports.serializeScene=P;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./index-BQoeJKCj.cjs");function P(n,e){const a={lines:n,curves:e.map(t=>({id:t.id,fromLineId:t.fromLineId,toLineId:t.toLineId,fromOffset:t.fromOffset,fromIsPercentage:t.fromIsPercentage??!1,toOffset:t.toOffset,toIsPercentage:t.toIsPercentage??!1}))};return JSON.stringify(a,null,2)}function O(n){let e;try{e=JSON.parse(n)}catch{throw new Error("deserializeScene: invalid JSON")}if(!e||typeof e!="object"||Array.isArray(e))throw new Error("deserializeScene: expected a JSON object");const a=e;if(!Array.isArray(a.lines))throw new Error('deserializeScene: missing "lines"');if(!Array.isArray(a.curves))throw new Error('deserializeScene: missing "curves"');return{lines:a.lines,curves:a.curves}}function h(n){const e=n.axleExecutions[n.axleExecutions.length-1],a=i.getCumulativeArcLength(n.path,e.segmentIndex,e.segmentDistance);return Math.max(0,n.path.totalDistance-a)}function d(n){const e=n.axleExecutions[0],a=i.getCumulativeArcLength(n.path,e.segmentIndex,e.segmentDistance);let t=0;for(let r=0;r<n.path.segments.length;r++){const o=n.path.segments[r];if(r>=e.segmentIndex&&o.type==="curve")return Math.max(0,t-a);t+=o.length}return null}function p(n,e,a){let t=a.maxSpeed;const r=Math.sqrt(2*a.deceleration*Math.max(0,n));if(t=Math.min(t,r),e!==null){const o=Math.sqrt(a.minCurveSpeed**2+2*a.deceleration*e);t=Math.min(t,o)}return Math.max(0,t)}function x(n,e,a,t,r){return n<e?Math.min(e,n+a*r):n>e?Math.max(e,n-t*r):n}function S(n,e,a,t,r,o){const c=h(e),l=d(e),f=p(c,l,t),g=x(a.currentSpeed,f,t.acceleration,t.deceleration,r),u=g*r,v=n.axles.map(s=>({lineId:s.lineId,position:s.position,absoluteOffset:s.offset})),L=e.axleExecutions.map(s=>({currentSegmentIndex:s.segmentIndex,segmentDistance:s.segmentDistance})),m=i.moveVehicle(v,L,e.path,u,o,e.curveDataMap);return{state:{axles:m.axles.map(s=>({lineId:s.lineId,offset:s.absoluteOffset,position:s.position})),axleSpacings:n.axleSpacings},execution:{...e,axleExecutions:m.axleExecutions.map(s=>({segmentIndex:s.currentSegmentIndex,segmentDistance:s.segmentDistance}))},accelState:{currentSpeed:g},arrived:m.arrived}}function A(n,e){const a=e.end.x-e.start.x,t=e.end.y-e.start.y,r=a*a+t*t;if(r===0)return{offset:0,distance:Math.sqrt((n.x-e.start.x)**2+(n.y-e.start.y)**2)};const o=Math.max(0,Math.min(1,((n.x-e.start.x)*a+(n.y-e.start.y)*t)/r)),c=e.start.x+o*a,l=e.start.y+o*t,f=Math.sqrt((n.x-c)**2+(n.y-l)**2);return{offset:o*Math.sqrt(r),distance:f}}function I(n,e){const a=i.getLineLength(n),t=e.reduce((o,c)=>o+c,0);return[0,Math.max(0,a-t)]}function M(n,e){let a=0;for(const t of e)t.fromLineId===n&&!t.fromIsPercentage&&t.fromOffset!==void 0&&(a=Math.max(a,t.fromOffset)),t.toLineId===n&&!t.toIsPercentage&&t.toOffset!==void 0&&(a=Math.max(a,t.toOffset));return a}exports.PathEngine=i.PathEngine;exports.arcLengthToSegmentPosition=i.arcLengthToSegmentPosition;exports.buildArcLengthTable=i.buildArcLengthTable;exports.buildGraph=i.buildGraph;exports.calculateBezierArcLength=i.calculateBezierArcLength;exports.calculateFrontAxlePosition=i.calculateFrontAxlePosition;exports.calculateInitialAxlePositions=i.calculateInitialAxlePositions;exports.calculatePositionOnCurve=i.calculatePositionOnCurve;exports.calculatePositionOnLine=i.calculatePositionOnLine;exports.calculateTangentLength=i.calculateTangentLength;exports.createBezierCurve=i.createBezierCurve;exports.createInitialMovementState=i.createInitialMovementState;exports.distance=i.distance;exports.distanceToT=i.distanceToT;exports.findPath=i.findPath;exports.getArcLength=i.getArcLength;exports.getCumulativeArcLength=i.getCumulativeArcLength;exports.getLineLength=i.getLineLength;exports.getPointOnBezier=i.getPointOnBezier;exports.getPointOnLine=i.getPointOnLine;exports.getPointOnLineByOffset=i.getPointOnLineByOffset;exports.getPositionFromOffset=i.getPositionFromOffset;exports.handleArrival=i.handleArrival;exports.initializeAllVehicles=i.initializeAllVehicles;exports.initializeMovingVehicle=i.initializeMovingVehicle;exports.isPointNearPoint=i.isPointNearPoint;exports.moveVehicle=i.moveVehicle;exports.normalize=i.normalize;exports.prepareCommandPath=i.prepareCommandPath;exports.resolveFromLineOffset=i.resolveFromLineOffset;exports.resolveToLineOffset=i.resolveToLineOffset;exports.updateAxlePosition=i.updateAxlePosition;exports.approachSpeed=x;exports.computeDistToNextCurve=d;exports.computeMinLineLength=M;exports.computeRemainingToArrival=h;exports.computeTargetSpeed=p;exports.deserializeScene=O;exports.getValidRearOffsetRange=I;exports.moveVehicleWithAcceleration=S;exports.projectPointOnLine=A;exports.serializeScene=P;
package/dist/core.js CHANGED
@@ -1,5 +1,5 @@
1
- import { g as u, m as x, a as p } from "./index-okJUn3WJ.js";
2
- import { P as T, b, c as j, d as q, e as C, f as N, h as B, i as V, j as J, k as F, l as R, n as k, o as G, p as W, q as X, r as Y, s as H, t as K, u as Q, v as U, w as Z, x as _, y as $, z as ee, A as te, B as ne, C as ae, D as se, E as re } from "./index-okJUn3WJ.js";
1
+ import { g as u, m as x, a as p } from "./index-DUYG8fxI.js";
2
+ import { P as T, b, c as j, d as q, e as C, f as N, h as B, i as V, j as J, k as F, l as R, n as k, o as G, p as W, q as X, r as Y, s as H, t as K, u as Q, v as U, w as Z, x as _, y as $, z as ee, A as te, B as ne, C as ae, D as se, E as re } from "./index-DUYG8fxI.js";
3
3
  function P(n, e) {
4
4
  const a = {
5
5
  lines: n,
@@ -0,0 +1 @@
1
+ "use strict";function L(t,e){const s=e.x-t.x,n=e.y-t.y;return Math.sqrt(s*s+n*n)}function T(t,e){const s=e.x-t.x,n=e.y-t.y,i=Math.sqrt(s*s+n*n);return i===0?{x:0,y:0}:{x:s/i,y:n/i}}function F(t,e){return e*(t==="proportional-40"?.4:.5522)}function z(t,e,s,n){const{tangentMode:i}=s;let c;n?.fromOffset!==void 0?c=V(t,n.fromOffset,n.fromIsPercentage??!1):c=t.end;let o;n?.toOffset!==void 0?o=V(e,n.toOffset,n.toIsPercentage??!1):o=e.start;const r=T(t.start,t.end),f=T(e.start,e.end),h=L(c,o),a=F(i,h),l={x:c.x+r.x*a,y:c.y+r.y*a},d={x:o.x-f.x*a,y:o.y-f.y*a};return{p0:c,p1:l,p2:d,p3:o}}function N(t,e){return{x:t.start.x+(t.end.x-t.start.x)*e,y:t.start.y+(t.end.y-t.start.y)*e}}function V(t,e,s){const n=L(t.start,t.end);let i;return s?i=e:i=n>0?e/n:0,i=Math.max(0,Math.min(1,i)),N(t,i)}function S(t,e){const{p0:s,p1:n,p2:i,p3:c}=t,o=1-e,r=o*o,f=r*o,h=e*e,a=h*e;return{x:f*s.x+3*r*e*n.x+3*o*h*i.x+a*c.x,y:f*s.y+3*r*e*n.y+3*o*h*i.y+a*c.y}}function ee(t,e,s=10){return L(t,e)<=s}function $(t,e=100){const s=[{t:0,distance:0}];let n=t.p0,i=0;for(let c=1;c<=e;c++){const o=c/e,r=S(t,o);i+=L(n,r),s.push({t:o,distance:i}),n=r}return s}function j(t,e){if(e<=0)return 0;const s=t[t.length-1].distance;if(e>=s)return 1;let n=0,i=t.length-1;for(;n<i-1;){const a=Math.floor((n+i)/2);t[a].distance<e?n=a:i=a}const c=t[n].distance,o=t[i].distance,r=t[n].t,f=t[i].t;if(o===c)return r;const h=(e-c)/(o-c);return r+h*(f-r)}function te(t){return t[t.length-1].distance}function K(t,e=100){let s=0,n=t.p0;for(let i=1;i<=e;i++){const c=i/e,o=S(t,c);s+=L(n,o),n=o}return s}function b(t,e,s,n){const i=L(t.start,t.end);return e===void 0?n*i:s?Math.max(0,Math.min(e,1))*i:Math.max(0,Math.min(e,i))}function B(t,e,s,n){const i=L(t.start,t.end);return e===void 0?n*i:s?Math.max(0,Math.min(e,1))*i:Math.max(0,Math.min(e,i))}function x(t,e,s){const n=new Map,i=new Map,c=new Map;for(const o of t)i.set(o.id,o),c.set(o.id,L(o.start,o.end)),n.set(o.id,[]);for(let o=0;o<e.length;o++){const r=e[o],f=i.get(r.fromLineId),h=i.get(r.toLineId);if(!f||!h)continue;const a=b(f,r.fromOffset,r.fromIsPercentage,1),l=B(h,r.toOffset,r.toIsPercentage,0),d=z(f,h,s,{fromOffset:a,fromIsPercentage:!1,toOffset:l,toIsPercentage:!1}),g=K(d),v={curveIndex:o,fromLineId:r.fromLineId,toLineId:r.toLineId,fromOffset:a,toOffset:l,curveLength:g};n.get(r.fromLineId).push(v)}return{adjacency:n,lines:i,lineLengths:c}}function E(t,e){return t.curveCount!==e.curveCount?t.curveCount-e.curveCount:t.totalDistance-e.totalDistance}function k(t,e,s,n,i=!1){const{adjacency:c,lines:o,lineLengths:r}=t;if(!o.get(s))return null;const h=r.get(s),a=i?n/100*h:n,l=[],d=new Map,g=(u,p)=>`${u}:${Math.round(p)}`;if(e.lineId===s&&a>=e.offset){const u=a-e.offset;return{segments:[{type:"line",lineId:e.lineId,startOffset:e.offset,endOffset:a,length:u}],totalDistance:u,curveCount:0}}const v=c.get(e.lineId)||[];for(const u of v){if(u.fromOffset<e.offset)continue;const p=u.fromOffset-e.offset,I=p+u.curveLength,O={type:"line",lineId:e.lineId,startOffset:e.offset,endOffset:u.fromOffset,length:p},m={type:"curve",curveIndex:u.curveIndex,startOffset:0,endOffset:u.curveLength,length:u.curveLength};l.push({lineId:u.toLineId,entryOffset:u.toOffset,totalDistance:I,curveCount:1,path:[O,m]})}for(l.sort(E);l.length>0;){const u=l.shift(),p=g(u.lineId,u.entryOffset),I=d.get(p);if(I!==void 0&&(I.curveCount<u.curveCount||I.curveCount===u.curveCount&&I.distance<=u.totalDistance))continue;if(d.set(p,{curveCount:u.curveCount,distance:u.totalDistance}),u.lineId===s){const m=Math.abs(a-u.entryOffset);if(a>=u.entryOffset){const C={type:"line",lineId:s,startOffset:u.entryOffset,endOffset:a,length:m};return{segments:[...u.path,C],totalDistance:u.totalDistance+m,curveCount:u.curveCount}}}const O=c.get(u.lineId)||[];for(const m of O){if(m.fromOffset<u.entryOffset)continue;const C=m.fromOffset-u.entryOffset,q=u.totalDistance+C+m.curveLength,w=u.curveCount+1,X=g(m.toLineId,m.toOffset),P=d.get(X);if(P!==void 0&&(P.curveCount<w||P.curveCount===w&&P.distance<=q))continue;const Y={type:"line",lineId:u.lineId,startOffset:u.entryOffset,endOffset:m.fromOffset,length:C},Z={type:"curve",curveIndex:m.curveIndex,startOffset:0,endOffset:m.curveLength,length:m.curveLength};l.push({lineId:m.toLineId,entryOffset:m.toOffset,totalDistance:q,curveCount:w,path:[...u.path,Y,Z]})}l.sort(E)}return null}function A(t,e){const s=Math.sqrt(Math.pow(t.end.x-t.start.x,2)+Math.pow(t.end.y-t.start.y,2)),n=s>0?e/s:0;return{x:t.start.x+(t.end.x-t.start.x)*Math.min(1,Math.max(0,n)),y:t.start.y+(t.end.y-t.start.y)*Math.min(1,Math.max(0,n))}}function M(t){return Math.sqrt(Math.pow(t.end.x-t.start.x,2)+Math.pow(t.end.y-t.start.y,2))}function G(t,e,s){let n=0;for(let i=0;i<e;i++)n+=t.segments[i].length;return n+=s,n}function R(t,e){let s=0;for(let n=0;n<t.segments.length;n++){const i=t.segments[n],c=s+i.length;if(e<c)return{segmentIndex:n,segmentDistance:e-s};if(e===c)return n+1<t.segments.length?{segmentIndex:n+1,segmentDistance:0}:{segmentIndex:n,segmentDistance:i.length};s+=i.length}return null}function ne(t,e,s,n){const c=G(t,e,s)+n;return R(t,c)}function Q(t,e,s,n){const i=M(n),c=s.length+1,o=new Array(c);o[c-1]={lineId:t,absoluteOffset:e,position:A(n,e)};let r=e;for(let f=c-2;f>=0;f--)r=Math.min(r+s[f],i),o[f]={lineId:t,absoluteOffset:r,position:A(n,r)};return o}function W(t,e){return{...t,state:"idle"}}function _(t){return{vehicle:t,execution:null}}function se(t,e){const s=[],n=new Map;for(const i of t){if(!e.get(i.lineId))continue;const o=W(i);s.push(o);const r=_(o);n.set(i.id,r)}return{movingVehicles:s,stateMap:n}}function y(t,e){return{position:A(t,e),lineId:t.id,absoluteOffset:e}}function D(t,e){const s=j(t.arcLengthTable,e);return{position:S(t.bezier,s)}}function H(t,e,s,n,i,c,o){const r=s.segments[e.currentSegmentIndex],f=e.segmentDistance+n;if(f>=r.length){const a=f-r.length,l=e.currentSegmentIndex+1;if(l>=s.segments.length){if(o!==void 0&&r.type==="line"){const u=i.get(r.lineId),p=r.startOffset+f;if(p<=o){const O=y(u,p);return{axleState:{...t,...O},execution:{...e,segmentDistance:f},completed:!1}}const I=y(u,o);return{axleState:{...t,...I},execution:{...e,segmentDistance:o-r.startOffset},completed:!0}}const v=r.type==="line"?y(i.get(r.lineId),r.endOffset):D(c.get(r.curveIndex),r.length);return{axleState:{...t,...v},execution:{...e,segmentDistance:r.length},completed:!0}}const d=s.segments[l],g=d.type==="line"?y(i.get(d.lineId),d.startOffset+a):D(c.get(d.curveIndex),a);return{axleState:{...t,...g},execution:{currentSegmentIndex:l,segmentDistance:a},completed:!1}}const h=r.type==="line"?y(i.get(r.lineId),r.startOffset+f):D(c.get(r.curveIndex),f);return{axleState:{...t,...h},execution:{...e,segmentDistance:f},completed:!1}}function ie(t,e,s,n){const i=new Map;for(const c of t.segments)if(c.type==="curve"&&c.curveIndex!==void 0){const o=e[c.curveIndex];if(o){const r=s.get(o.fromLineId),f=s.get(o.toLineId);if(r&&f){const h=b(r,o.fromOffset,o.fromIsPercentage,1),a=B(f,o.toOffset,o.toIsPercentage,0),l=z(r,f,n,{fromOffset:h,fromIsPercentage:!1,toOffset:a,toIsPercentage:!1}),d=$(l);i.set(c.curveIndex,{bezier:l,arcLengthTable:d})}}}return i}function J(t,e,s){const{graph:n,linesMap:i,curves:c,config:o}=s,r=i.get(e.targetLineId);if(!r)return null;const f=t.axleSpacings.reduce((u,p)=>u+p,0),a=M(r)-f;if(a<=0)return null;const l=e.isPercentage?e.targetOffset*a:Math.min(e.targetOffset,a),d=t.axles[t.axles.length-1],g=k(n,{lineId:d.lineId,offset:d.absoluteOffset},e.targetLineId,l,!1);if(!g)return null;const v=ie(g,c,i,o);return{path:g,curveDataMap:v}}function re(t,e){const s=t.execution,i=e.vehicleQueues.get(t.vehicle.id)?.[s.currentCommandIndex];return i&&e.onCommandComplete&&e.onCommandComplete({vehicleId:t.vehicle.id,command:i,finalPosition:{lineId:t.vehicle.axles[t.vehicle.axles.length-1].lineId,absoluteOffset:t.vehicle.axles[t.vehicle.axles.length-1].absoluteOffset,position:t.vehicle.axles[t.vehicle.axles.length-1].position},payload:i.payload}),{handled:!0,vehicle:{...t.vehicle,state:"waiting"},newExecution:s,isWaiting:!0}}function U(t,e,s,n,i,c){const o=t.map((r,f)=>{const h=f===t.length-1;let a;if(!h){const l=e[f];if(l.currentSegmentIndex<s.segments.length){const d=s.segments[l.currentSegmentIndex];if(d.type==="line"){const g=i.get(d.lineId);g&&(a=M(g))}}}return H(r,e[f],s,n,i,c,a)});return{axles:o.map(r=>r.axleState),axleExecutions:o.map(r=>r.execution),arrived:o[o.length-1].completed}}class oe{graph=null;linesMap=new Map;curves=[];config;constructor(e){this.config={tangentMode:e.tangentMode}}get movementConfig(){return this.config}get lines(){return Array.from(this.linesMap.values())}getCurves(){return this.curves}setScene(e,s){this.linesMap.clear();for(const n of e)this.linesMap.set(n.id,n);this.curves=s,this.graph=x(e,s,this.config)}addLine(e){return this.linesMap.has(e.id)?!1:(this.linesMap.set(e.id,e),this.graph=x(Array.from(this.linesMap.values()),this.curves,this.config),!0)}updateLine(e,s){const n=this.linesMap.get(e);return n?(s.start&&(n.start=s.start),s.end&&(n.end=s.end),this.graph=x(Array.from(this.linesMap.values()),this.curves,this.config),!0):!1}updateLineEndpoint(e,s,n){return this.updateLine(e,{[s]:n})}renameLine(e,s){const n=s.trim();if(!n)return{success:!1,error:"Name cannot be empty"};if(n===e)return{success:!0};if(this.linesMap.has(n))return{success:!1,error:`"${n}" already exists`};const i=this.linesMap.get(e);if(!i)return{success:!1,error:`Line "${e}" not found`};i.id=n,this.linesMap.delete(e),this.linesMap.set(n,i);for(const c of this.curves)c.fromLineId===e&&(c.fromLineId=n),c.toLineId===e&&(c.toLineId=n);return this.graph=x(Array.from(this.linesMap.values()),this.curves,this.config),{success:!0}}removeLine(e){return this.linesMap.has(e)?(this.linesMap.delete(e),this.curves=this.curves.filter(s=>s.fromLineId!==e&&s.toLineId!==e),this.graph=x(Array.from(this.linesMap.values()),this.curves,this.config),!0):!1}addCurve(e){this.curves.push(e),this.graph=x(Array.from(this.linesMap.values()),this.curves,this.config)}updateCurve(e,s){return e<0||e>=this.curves.length?!1:(this.curves[e]={...this.curves[e],...s},this.graph=x(Array.from(this.linesMap.values()),this.curves,this.config),!0)}removeCurve(e){return e<0||e>=this.curves.length?!1:(this.curves.splice(e,1),this.graph=x(Array.from(this.linesMap.values()),this.curves,this.config),!0)}initializeVehicle(e,s,n){const i=this.linesMap.get(e);if(!i)return null;const{axleSpacings:c}=n;if(c.length===0)throw new Error("initializeVehicle: axleSpacings must have at least one entry (vehicle needs ≥2 axles)");const o=c.reduce((a,l)=>a+l,0),r=M(i),f=Math.min(s,r-o);return{axles:Q(e,f,c,i).map(a=>({lineId:a.lineId,offset:a.absoluteOffset,position:a.position})),axleSpacings:c}}preparePath(e,s,n,i=!1){if(!this.graph)return null;const c=e.axleSpacings.reduce((g,v)=>g+v,0),o=e.axles[e.axles.length-1],r={lineId:o.lineId,offset:o.offset,axles:e.axles.map(g=>({lineId:g.lineId,position:g.position,absoluteOffset:g.offset})),axleSpacings:e.axleSpacings},f=J(r,{targetLineId:s,targetOffset:n,isPercentage:i},{graph:this.graph,linesMap:this.linesMap,curves:this.curves,config:this.config});if(!f)return null;let h=n;const a=this.linesMap.get(s);if(a){const g=Math.max(0,M(a)-c);h=i?n*g:Math.min(n,g)}let l=0;const d=[{segmentIndex:0,segmentDistance:c}];for(let g=0;g<e.axleSpacings.length;g++)l+=e.axleSpacings[g],d.push({segmentIndex:0,segmentDistance:c-l});return{path:f.path,curveDataMap:f.curveDataMap,axleExecutions:d,targetLineId:s,targetOffset:h}}moveVehicle(e,s,n){const i=e.axles.map(r=>({lineId:r.lineId,position:r.position,absoluteOffset:r.offset})),c=s.axleExecutions.map(r=>({currentSegmentIndex:r.segmentIndex,segmentDistance:r.segmentDistance})),o=U(i,c,s.path,n,this.linesMap,s.curveDataMap);return{state:{axles:o.axles.map(r=>({lineId:r.lineId,offset:r.absoluteOffset,position:r.position})),axleSpacings:e.axleSpacings},execution:{...s,axleExecutions:o.axleExecutions.map(r=>({segmentIndex:r.currentSegmentIndex,segmentDistance:r.segmentDistance}))},arrived:o.arrived}}}exports.PathEngine=oe;exports.arcLengthToSegmentPosition=R;exports.buildArcLengthTable=$;exports.buildGraph=x;exports.calculateBezierArcLength=K;exports.calculateFrontAxlePosition=ne;exports.calculateInitialAxlePositions=Q;exports.calculatePositionOnCurve=D;exports.calculatePositionOnLine=y;exports.calculateTangentLength=F;exports.createBezierCurve=z;exports.createInitialMovementState=_;exports.distance=L;exports.distanceToT=j;exports.findPath=k;exports.getArcLength=te;exports.getCumulativeArcLength=G;exports.getLineLength=M;exports.getPointOnBezier=S;exports.getPointOnLine=N;exports.getPointOnLineByOffset=V;exports.getPositionFromOffset=A;exports.handleArrival=re;exports.initializeAllVehicles=se;exports.initializeMovingVehicle=W;exports.isPointNearPoint=ee;exports.moveVehicle=U;exports.normalize=T;exports.prepareCommandPath=J;exports.resolveFromLineOffset=b;exports.resolveToLineOffset=B;exports.updateAxlePosition=H;