vehicle-path2 3.0.0 → 3.0.1
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 +234 -113
- package/dist/core/algorithms/acceleration.d.ts +75 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core.cjs +1 -1
- package/dist/core.js +149 -76
- package/dist/{index-BV93143R.js → index-C7pH7jZo.js} +26 -26
- package/dist/vehicle-path.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# vehicle-path2
|
|
2
2
|
|
|
3
|
-
Library untuk simulasi pergerakan kendaraan
|
|
3
|
+
Library untuk simulasi pergerakan kendaraan multi-axle sepanjang jalur yang terdiri dari Lines dan Curves.
|
|
4
4
|
|
|
5
5
|
## Instalasi
|
|
6
6
|
|
|
@@ -8,161 +8,282 @@ Library untuk simulasi pergerakan kendaraan dual-axle sepanjang jalur.
|
|
|
8
8
|
npm install vehicle-path2
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Konsep Dasar
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
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.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
const sim = useVehicleSimulation({ wheelbase: 30 })
|
|
17
|
+
Titik acuan kendaraan adalah **axle paling belakang** (`axles[N-1]`). Semua axle lainnya dihitung ke depan berdasarkan `axleSpacings`.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
sim.addLine({ id: 'line1', start: [0, 0], end: [400, 0] })
|
|
21
|
-
sim.addLine({ id: 'line2', start: [400, 0], end: [400, 300] })
|
|
22
|
-
sim.connect('line1', 'line2')
|
|
19
|
+
---
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
sim.addVehicles({ id: 'v1', lineId: 'line1', position: 0 })
|
|
21
|
+
## Setup
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
```typescript
|
|
24
|
+
import { PathEngine } from 'vehicle-path2/core'
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
26
|
+
const engine = new PathEngine({
|
|
27
|
+
maxWheelbase: 100, // batas maksimum total panjang kendaraan
|
|
28
|
+
tangentMode: 'proportional-40' // mode kurva bezier
|
|
29
|
+
})
|
|
34
30
|
```
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
---
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
## Manajemen Scene
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
- `0` = 0% (awal line)
|
|
42
|
-
- `0.5` = 50% (tengah line)
|
|
43
|
-
- `1` = 100% (ujung line)
|
|
36
|
+
### Lines
|
|
44
37
|
|
|
45
|
-
|
|
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
|
|
46
42
|
|
|
47
|
-
|
|
43
|
+
// Update posisi titik
|
|
44
|
+
engine.updateLine('L1', { end: { x: 500, y: 0 } })
|
|
45
|
+
engine.updateLineEndpoint('L1', 'start', { x: 10, y: 0 })
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
tangentMode: 'proportional-40' // mode kurva (opsional)
|
|
53
|
-
})
|
|
54
|
-
```
|
|
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: '...' }
|
|
55
50
|
|
|
56
|
-
|
|
51
|
+
// Hapus — cascade: semua curve yang terhubung ke line ini ikut dihapus
|
|
52
|
+
engine.removeLine('L1')
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
sim.updateLine('line1', { end: [500, 100] })
|
|
61
|
-
sim.removeLine('line1')
|
|
62
|
-
sim.clearScene()
|
|
54
|
+
// Baca semua lines
|
|
55
|
+
engine.lines // → Line[]
|
|
63
56
|
```
|
|
64
57
|
|
|
65
|
-
|
|
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.
|
|
66
67
|
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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[]
|
|
74
99
|
```
|
|
75
100
|
|
|
76
|
-
|
|
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
|
+
---
|
|
77
106
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
sim.updateVehicle('v1', { lineId: 'line2', position: 0.8 }) // pindah ke 80% di line2
|
|
84
|
-
sim.removeVehicle('v1')
|
|
85
|
-
sim.clearVehicles()
|
|
107
|
+
### Muat Scene Sekaligus
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Replace seluruh scene secara atomik
|
|
111
|
+
engine.setScene(lines, curves)
|
|
86
112
|
```
|
|
87
113
|
|
|
88
|
-
|
|
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
|
+
}
|
|
89
131
|
|
|
90
|
-
|
|
91
|
-
sim.goto({ id: 'v1', lineId: 'line2' }) // default position = 1.0 (ujung)
|
|
92
|
-
sim.goto({ id: 'v1', lineId: 'line2', position: 0.5 }) // 0.5 = tengah line
|
|
93
|
-
sim.goto({ id: 'v1', lineId: 'line2', position: 150, isPercentage: false }) // absolute
|
|
94
|
-
sim.goto({ id: 'v1', lineId: 'line2', payload: { orderId: '123' } }) // dengan payload
|
|
95
|
-
sim.clearQueue('v1')
|
|
132
|
+
const truck: MyVehicle = { id: 'v1', name: 'Truck A', axleSpacings: [40, 40] } // 3 axle
|
|
96
133
|
```
|
|
97
134
|
|
|
98
|
-
|
|
135
|
+
> `axleSpacings[i]` = jarak arc-length antara `axles[i]` dan `axles[i+1]`. Array harus memiliki minimal 1 entry.
|
|
99
136
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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]
|
|
106
158
|
```
|
|
107
159
|
|
|
108
|
-
###
|
|
160
|
+
### 2. `preparePath` — Tentukan tujuan
|
|
161
|
+
|
|
162
|
+
Jalankan Dijkstra untuk mencari rute dari posisi saat ini ke tujuan. Dipanggil **sekali** sebelum animasi dimulai.
|
|
109
163
|
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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)
|
|
116
173
|
```
|
|
117
174
|
|
|
118
|
-
###
|
|
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
|
|
119
188
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
sim.vehicles // kendaraan (posisi awal)
|
|
124
|
-
sim.movingVehicles // kendaraan (posisi saat animasi)
|
|
189
|
+
if (result.arrived) {
|
|
190
|
+
// Kendaraan sudah sampai tujuan
|
|
191
|
+
}
|
|
125
192
|
```
|
|
126
193
|
|
|
127
|
-
|
|
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' })
|
|
128
203
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
)
|
|
132
213
|
|
|
133
|
-
|
|
134
|
-
|
|
214
|
+
// Client mengelola data vehicle sendiri
|
|
215
|
+
const myVehicle = { id: 'v1', name: 'Truck', axleSpacings: [40] }
|
|
135
216
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
sim.addVehicles({ id: 'v1', lineId: 'line1', position: 0 })
|
|
139
|
-
sim.goto({ id: 'v1', lineId: 'line1', position: 1.0 })
|
|
140
|
-
sim.prepare()
|
|
217
|
+
// 1. Tempatkan di L1, axle belakang di posisi 0
|
|
218
|
+
let state: VehiclePathState = engine.initializeVehicle('L1', 0, myVehicle)!
|
|
141
219
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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) {
|
|
147
242
|
requestAnimationFrame(animate)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<svg width={600} height={200}>
|
|
152
|
-
{sim.movingVehicles.map(v => (
|
|
153
|
-
<circle
|
|
154
|
-
key={v.id}
|
|
155
|
-
cx={v.rear.position.x}
|
|
156
|
-
cy={v.rear.position.y}
|
|
157
|
-
r={10}
|
|
158
|
-
fill="blue"
|
|
159
|
-
/>
|
|
160
|
-
))}
|
|
161
|
-
</svg>
|
|
162
|
-
)
|
|
243
|
+
}
|
|
163
244
|
}
|
|
245
|
+
|
|
246
|
+
requestAnimationFrame(animate)
|
|
164
247
|
```
|
|
165
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
|
+
|
|
166
287
|
## License
|
|
167
288
|
|
|
168
289
|
MIT
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { VehiclePathState, PathExecution } from '../engine';
|
|
2
|
+
import type { Line } from '../types/geometry';
|
|
3
|
+
/**
|
|
4
|
+
* Konfigurasi acceleration/deceleration untuk vehicle.
|
|
5
|
+
* Nilai-nilai ini bersifat global (sama untuk semua segmen path).
|
|
6
|
+
*/
|
|
7
|
+
export interface AccelerationConfig {
|
|
8
|
+
/** px/s² — laju percepatan dari berhenti menuju maxSpeed */
|
|
9
|
+
acceleration: number;
|
|
10
|
+
/** px/s² — laju perlambatan (digunakan untuk curve dan arrival) */
|
|
11
|
+
deceleration: number;
|
|
12
|
+
/** px/s — kecepatan maksimum di garis lurus */
|
|
13
|
+
maxSpeed: number;
|
|
14
|
+
/** px/s — kecepatan minimum saat memasuki curve (tidak berhenti total) */
|
|
15
|
+
minCurveSpeed: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* State kecepatan kendaraan saat ini.
|
|
19
|
+
* Caller menyimpan ini di luar dan meneruskan ke setiap tick.
|
|
20
|
+
*/
|
|
21
|
+
export interface AccelerationState {
|
|
22
|
+
/** Kecepatan kendaraan saat ini dalam px/s */
|
|
23
|
+
currentSpeed: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Hitung jarak tersisa dari posisi rear axle ke akhir path (tujuan).
|
|
27
|
+
*/
|
|
28
|
+
export declare function computeRemainingToArrival(execution: PathExecution): number;
|
|
29
|
+
/**
|
|
30
|
+
* Hitung jarak dari posisi front axle ke awal segment curve berikutnya di path.
|
|
31
|
+
* Return 0 jika front axle sudah berada di dalam curve.
|
|
32
|
+
* Return null jika tidak ada curve lagi di depan.
|
|
33
|
+
*
|
|
34
|
+
* Menggunakan front axle (axleExecutions[0]) sebagai referensi — karena deceleration
|
|
35
|
+
* harus selesai (mencapai minCurveSpeed) tepat saat front axle secara visual memasuki
|
|
36
|
+
* curve. Jika menggunakan rear axle, kendaraan baru melambat setelah front axle
|
|
37
|
+
* sudah masuk curve.
|
|
38
|
+
*/
|
|
39
|
+
export declare function computeDistToNextCurve(execution: PathExecution): number | null;
|
|
40
|
+
/**
|
|
41
|
+
* Hitung target speed berdasarkan lookahead jarak ke arrival dan curve.
|
|
42
|
+
*
|
|
43
|
+
* Menggunakan formula fisika: v = sqrt(2 * a * d)
|
|
44
|
+
* - Arrival: target = sqrt(2 * decel * distToArrival) → berhenti di tujuan
|
|
45
|
+
* - Curve: target = sqrt(minCurveSpeed² + 2 * decel * distToNextCurve) → capai minCurveSpeed di curve
|
|
46
|
+
* - Batas atas: maxSpeed
|
|
47
|
+
*/
|
|
48
|
+
export declare function computeTargetSpeed(distToArrival: number, distToNextCurve: number | null, config: AccelerationConfig): number;
|
|
49
|
+
/**
|
|
50
|
+
* Sesuaikan kecepatan saat ini menuju target dengan laju acceleration/deceleration.
|
|
51
|
+
*/
|
|
52
|
+
export declare function approachSpeed(current: number, target: number, acceleration: number, deceleration: number, deltaTime: number): number;
|
|
53
|
+
/**
|
|
54
|
+
* Gerakkan vehicle per tick dengan efek acceleration dan deceleration.
|
|
55
|
+
*
|
|
56
|
+
* Versi eksperimental dari PathEngine.moveVehicle yang menambahkan:
|
|
57
|
+
* - Startup acceleration: kendaraan mulai dari berhenti dan mempercepat
|
|
58
|
+
* - Pre-curve deceleration: melambat sebelum memasuki curve
|
|
59
|
+
* - Arrival deceleration: melambat hingga berhenti total di tujuan
|
|
60
|
+
*
|
|
61
|
+
* Tidak memodifikasi PathEngine atau moveVehicle yang sudah ada.
|
|
62
|
+
*
|
|
63
|
+
* @param state - Posisi vehicle saat ini
|
|
64
|
+
* @param execution - Rencana rute (dari preparePath atau tick sebelumnya)
|
|
65
|
+
* @param accelState - State kecepatan saat ini (simpan antar tick)
|
|
66
|
+
* @param config - Parameter acceleration/deceleration
|
|
67
|
+
* @param deltaTime - Durasi frame dalam detik (misal: 1/60 untuk 60fps)
|
|
68
|
+
* @param linesMap - Map dari line ID ke Line object (buat dari engine.lines)
|
|
69
|
+
*/
|
|
70
|
+
export declare function moveVehicleWithAcceleration(state: VehiclePathState, execution: PathExecution, accelState: AccelerationState, config: AccelerationConfig, deltaTime: number, linesMap: Map<string, Line>): {
|
|
71
|
+
state: VehiclePathState;
|
|
72
|
+
execution: PathExecution;
|
|
73
|
+
accelState: AccelerationState;
|
|
74
|
+
arrived: boolean;
|
|
75
|
+
};
|
package/dist/core/index.d.ts
CHANGED
|
@@ -20,4 +20,5 @@ export { initializeMovingVehicle, createInitialMovementState, initializeAllVehic
|
|
|
20
20
|
export { PathEngine, type PathEngineConfig, type VehiclePathState, type PathExecution } from './engine';
|
|
21
21
|
export { distance, normalize, getPointOnLine, getPointOnLineByOffset, getPointOnBezier, createBezierCurve, buildArcLengthTable, distanceToT, getArcLength, calculateTangentLength, isPointNearPoint, type ArcLengthEntry, type CurveOffsetOptions } from './algorithms/math';
|
|
22
22
|
export { serializeScene, deserializeScene, type SceneSnapshot } from './snapshot';
|
|
23
|
+
export { moveVehicleWithAcceleration, computeRemainingToArrival, computeDistToNextCurve, computeTargetSpeed, approachSpeed, type AccelerationConfig, type AccelerationState } from './algorithms/acceleration';
|
|
23
24
|
export { projectPointOnLine, getValidRearOffsetRange, computeMinLineLength } from './algorithms/geometry';
|
package/dist/core.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./index-C4FckUel.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,98 +1,171 @@
|
|
|
1
|
-
import { g as
|
|
2
|
-
import { P as
|
|
3
|
-
function
|
|
4
|
-
const
|
|
5
|
-
lines:
|
|
6
|
-
curves:
|
|
7
|
-
id:
|
|
8
|
-
fromLineId:
|
|
9
|
-
toLineId:
|
|
10
|
-
fromOffset:
|
|
11
|
-
fromIsPercentage:
|
|
12
|
-
toOffset:
|
|
13
|
-
toIsPercentage:
|
|
1
|
+
import { g as u, m as x, a as p } from "./index-C7pH7jZo.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-C7pH7jZo.js";
|
|
3
|
+
function P(n, e) {
|
|
4
|
+
const a = {
|
|
5
|
+
lines: n,
|
|
6
|
+
curves: e.map((t) => ({
|
|
7
|
+
id: t.id,
|
|
8
|
+
fromLineId: t.fromLineId,
|
|
9
|
+
toLineId: t.toLineId,
|
|
10
|
+
fromOffset: t.fromOffset,
|
|
11
|
+
fromIsPercentage: t.fromIsPercentage ?? !1,
|
|
12
|
+
toOffset: t.toOffset,
|
|
13
|
+
toIsPercentage: t.toIsPercentage ?? !1
|
|
14
14
|
}))
|
|
15
15
|
};
|
|
16
|
-
return JSON.stringify(
|
|
16
|
+
return JSON.stringify(a, null, 2);
|
|
17
17
|
}
|
|
18
|
-
function
|
|
19
|
-
let
|
|
18
|
+
function M(n) {
|
|
19
|
+
let e;
|
|
20
20
|
try {
|
|
21
|
-
|
|
21
|
+
e = JSON.parse(n);
|
|
22
22
|
} catch {
|
|
23
23
|
throw new Error("deserializeScene: invalid JSON");
|
|
24
24
|
}
|
|
25
|
-
if (!
|
|
25
|
+
if (!e || typeof e != "object" || Array.isArray(e))
|
|
26
26
|
throw new Error("deserializeScene: expected a JSON object");
|
|
27
|
-
const
|
|
28
|
-
if (!Array.isArray(
|
|
29
|
-
if (!Array.isArray(
|
|
27
|
+
const a = e;
|
|
28
|
+
if (!Array.isArray(a.lines)) throw new Error('deserializeScene: missing "lines"');
|
|
29
|
+
if (!Array.isArray(a.curves)) throw new Error('deserializeScene: missing "curves"');
|
|
30
30
|
return {
|
|
31
|
-
lines:
|
|
32
|
-
curves:
|
|
31
|
+
lines: a.lines,
|
|
32
|
+
curves: a.curves
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
|
|
35
|
+
function v(n) {
|
|
36
|
+
const e = n.axleExecutions[n.axleExecutions.length - 1], a = u(
|
|
37
|
+
n.path,
|
|
38
|
+
e.segmentIndex,
|
|
39
|
+
e.segmentDistance
|
|
40
|
+
);
|
|
41
|
+
return Math.max(0, n.path.totalDistance - a);
|
|
42
|
+
}
|
|
43
|
+
function S(n) {
|
|
44
|
+
const e = n.axleExecutions[0], a = u(
|
|
45
|
+
n.path,
|
|
46
|
+
e.segmentIndex,
|
|
47
|
+
e.segmentDistance
|
|
48
|
+
);
|
|
49
|
+
let t = 0;
|
|
50
|
+
for (let s = 0; s < n.path.segments.length; s++) {
|
|
51
|
+
const i = n.path.segments[s];
|
|
52
|
+
if (s >= e.segmentIndex && i.type === "curve")
|
|
53
|
+
return Math.max(0, t - a);
|
|
54
|
+
t += i.length;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function L(n, e, a) {
|
|
59
|
+
let t = a.maxSpeed;
|
|
60
|
+
const s = Math.sqrt(2 * a.deceleration * Math.max(0, n));
|
|
61
|
+
if (t = Math.min(t, s), e !== null) {
|
|
62
|
+
const i = Math.sqrt(
|
|
63
|
+
a.minCurveSpeed ** 2 + 2 * a.deceleration * e
|
|
64
|
+
);
|
|
65
|
+
t = Math.min(t, i);
|
|
66
|
+
}
|
|
67
|
+
return Math.max(0, t);
|
|
68
|
+
}
|
|
69
|
+
function O(n, e, a, t, s) {
|
|
70
|
+
return n < e ? Math.min(e, n + a * s) : n > e ? Math.max(e, n - t * s) : n;
|
|
71
|
+
}
|
|
72
|
+
function y(n, e, a, t, s, i) {
|
|
73
|
+
const o = v(e), c = S(e), l = L(o, c, t), f = O(
|
|
74
|
+
a.currentSpeed,
|
|
75
|
+
l,
|
|
76
|
+
t.acceleration,
|
|
77
|
+
t.deceleration,
|
|
78
|
+
s
|
|
79
|
+
), d = f * s, h = n.axles.map((r) => ({
|
|
80
|
+
lineId: r.lineId,
|
|
81
|
+
position: r.position,
|
|
82
|
+
absoluteOffset: r.offset
|
|
83
|
+
})), g = e.axleExecutions.map((r) => ({
|
|
84
|
+
currentSegmentIndex: r.segmentIndex,
|
|
85
|
+
segmentDistance: r.segmentDistance
|
|
86
|
+
})), m = x(h, g, e.path, d, i, e.curveDataMap);
|
|
87
|
+
return {
|
|
88
|
+
state: {
|
|
89
|
+
axles: m.axles.map((r) => ({ lineId: r.lineId, offset: r.absoluteOffset, position: r.position })),
|
|
90
|
+
axleSpacings: n.axleSpacings
|
|
91
|
+
},
|
|
92
|
+
execution: {
|
|
93
|
+
...e,
|
|
94
|
+
axleExecutions: m.axleExecutions.map((r) => ({
|
|
95
|
+
segmentIndex: r.currentSegmentIndex,
|
|
96
|
+
segmentDistance: r.segmentDistance
|
|
97
|
+
}))
|
|
98
|
+
},
|
|
99
|
+
accelState: { currentSpeed: f },
|
|
100
|
+
arrived: m.arrived
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function A(n, e) {
|
|
104
|
+
const a = e.end.x - e.start.x, t = e.end.y - e.start.y, s = a * a + t * t;
|
|
105
|
+
if (s === 0)
|
|
38
106
|
return { offset: 0, distance: Math.sqrt(
|
|
39
|
-
(
|
|
107
|
+
(n.x - e.start.x) ** 2 + (n.y - e.start.y) ** 2
|
|
40
108
|
) };
|
|
41
|
-
const
|
|
109
|
+
const i = Math.max(
|
|
42
110
|
0,
|
|
43
111
|
Math.min(
|
|
44
112
|
1,
|
|
45
|
-
((
|
|
113
|
+
((n.x - e.start.x) * a + (n.y - e.start.y) * t) / s
|
|
46
114
|
)
|
|
47
|
-
),
|
|
48
|
-
return { offset:
|
|
115
|
+
), o = e.start.x + i * a, c = e.start.y + i * t, l = Math.sqrt((n.x - o) ** 2 + (n.y - c) ** 2);
|
|
116
|
+
return { offset: i * Math.sqrt(s), distance: l };
|
|
49
117
|
}
|
|
50
|
-
function
|
|
51
|
-
const
|
|
52
|
-
return [0, Math.max(0,
|
|
118
|
+
function E(n, e) {
|
|
119
|
+
const a = p(n), t = e.reduce((i, o) => i + o, 0);
|
|
120
|
+
return [0, Math.max(0, a - t)];
|
|
53
121
|
}
|
|
54
|
-
function
|
|
55
|
-
let
|
|
56
|
-
for (const
|
|
57
|
-
|
|
58
|
-
return
|
|
122
|
+
function z(n, e) {
|
|
123
|
+
let a = 0;
|
|
124
|
+
for (const t of e)
|
|
125
|
+
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));
|
|
126
|
+
return a;
|
|
59
127
|
}
|
|
60
128
|
export {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
129
|
+
T as PathEngine,
|
|
130
|
+
O as approachSpeed,
|
|
131
|
+
b as arcLengthToSegmentPosition,
|
|
132
|
+
j as buildArcLengthTable,
|
|
133
|
+
q as buildGraph,
|
|
134
|
+
C as calculateBezierArcLength,
|
|
135
|
+
N as calculateFrontAxlePosition,
|
|
136
|
+
B as calculateInitialAxlePositions,
|
|
137
|
+
V as calculatePositionOnCurve,
|
|
138
|
+
J as calculatePositionOnLine,
|
|
139
|
+
F as calculateTangentLength,
|
|
140
|
+
S as computeDistToNextCurve,
|
|
141
|
+
z as computeMinLineLength,
|
|
142
|
+
v as computeRemainingToArrival,
|
|
143
|
+
L as computeTargetSpeed,
|
|
144
|
+
R as createBezierCurve,
|
|
145
|
+
k as createInitialMovementState,
|
|
146
|
+
M as deserializeScene,
|
|
147
|
+
G as distance,
|
|
148
|
+
W as distanceToT,
|
|
149
|
+
X as findPath,
|
|
150
|
+
Y as getArcLength,
|
|
151
|
+
u as getCumulativeArcLength,
|
|
152
|
+
p as getLineLength,
|
|
153
|
+
H as getPointOnBezier,
|
|
154
|
+
K as getPointOnLine,
|
|
155
|
+
Q as getPointOnLineByOffset,
|
|
156
|
+
U as getPositionFromOffset,
|
|
157
|
+
E as getValidRearOffsetRange,
|
|
158
|
+
Z as handleArrival,
|
|
159
|
+
_ as initializeAllVehicles,
|
|
160
|
+
$ as initializeMovingVehicle,
|
|
161
|
+
ee as isPointNearPoint,
|
|
162
|
+
x as moveVehicle,
|
|
163
|
+
y as moveVehicleWithAcceleration,
|
|
164
|
+
te as normalize,
|
|
165
|
+
ne as prepareCommandPath,
|
|
166
|
+
A as projectPointOnLine,
|
|
167
|
+
ae as resolveFromLineOffset,
|
|
168
|
+
se as resolveToLineOffset,
|
|
169
|
+
P as serializeScene,
|
|
170
|
+
re as updateAxlePosition
|
|
98
171
|
};
|
|
@@ -687,30 +687,30 @@ export {
|
|
|
687
687
|
E as D,
|
|
688
688
|
X as E,
|
|
689
689
|
ae as P,
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
690
|
+
O as a,
|
|
691
|
+
R as b,
|
|
692
|
+
N as c,
|
|
693
|
+
I as d,
|
|
694
|
+
F as e,
|
|
695
|
+
ie as f,
|
|
696
|
+
Q as g,
|
|
697
|
+
H as h,
|
|
698
|
+
w as i,
|
|
699
|
+
M as j,
|
|
700
|
+
k,
|
|
701
|
+
W as l,
|
|
702
|
+
ee as m,
|
|
703
|
+
U as n,
|
|
704
|
+
y as o,
|
|
705
|
+
_ as p,
|
|
706
|
+
G as q,
|
|
707
|
+
se as r,
|
|
708
|
+
b as s,
|
|
709
|
+
K as t,
|
|
710
|
+
V as u,
|
|
711
|
+
A as v,
|
|
712
|
+
oe as w,
|
|
713
|
+
re as x,
|
|
714
|
+
J as y,
|
|
715
|
+
ne as z
|
|
716
716
|
};
|
package/dist/vehicle-path.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as i,
|
|
1
|
+
import { P as i, b as t, c as s, d as n, e as o, f as l, h as r, i as c, j as g, l as h, n as P, o as u, p as L, q as f, r as m, g as v, a as A, s as d, t as O, u as p, v as z, w as x, x as B, y as C, m as T, A as b, B as F, C as V, D as y, E } from "./index-C7pH7jZo.js";
|
|
2
2
|
export {
|
|
3
3
|
i as PathEngine,
|
|
4
4
|
t as arcLengthToSegmentPosition,
|
|
@@ -19,8 +19,8 @@ export {
|
|
|
19
19
|
A as getLineLength,
|
|
20
20
|
d as getPointOnBezier,
|
|
21
21
|
O as getPointOnLine,
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
p as getPointOnLineByOffset,
|
|
23
|
+
z as getPositionFromOffset,
|
|
24
24
|
x as handleArrival,
|
|
25
25
|
B as initializeAllVehicles,
|
|
26
26
|
C as initializeMovingVehicle,
|
|
@@ -28,6 +28,6 @@ export {
|
|
|
28
28
|
b as normalize,
|
|
29
29
|
F as prepareCommandPath,
|
|
30
30
|
V as resolveFromLineOffset,
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
y as resolveToLineOffset,
|
|
32
|
+
E as updateAxlePosition
|
|
33
33
|
};
|
package/package.json
CHANGED