vehicle-path2 4.0.2 → 4.0.3
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 +287 -289
- package/dist/core/algorithms/pathFinding.d.ts +2 -0
- package/dist/core/engine.d.ts +50 -10
- package/dist/core/types/geometry.d.ts +1 -0
- package/dist/core.cjs +1 -1
- package/dist/core.js +73 -141
- package/dist/index-BObuJxsC.js +840 -0
- package/dist/index-BpTZXV22.cjs +1 -0
- package/dist/vehicle-path.cjs +1 -1
- package/dist/vehicle-path.js +9 -9
- package/package.json +83 -83
- package/dist/index-BQoeJKCj.cjs +0 -1
- package/dist/index-DUYG8fxI.js +0 -699
package/README.md
CHANGED
|
@@ -1,289 +1,287 @@
|
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
engine.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
> **
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
// state
|
|
155
|
-
// state.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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 (returns curve id — auto-generated if not provided)
|
|
72
|
+
const curveId = engine.addCurve({
|
|
73
|
+
id: 'c1', // optional — auto-generated if omitted
|
|
74
|
+
fromLineId: 'L1',
|
|
75
|
+
toLineId: 'L2',
|
|
76
|
+
fromOffset: 380, // posisi di L1 (px dari start)
|
|
77
|
+
fromIsPercentage: false,
|
|
78
|
+
toOffset: 20, // posisi di L2 (px dari start)
|
|
79
|
+
toIsPercentage: false,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Update curve berdasarkan id
|
|
83
|
+
engine.updateCurve('c1', { fromOffset: 100 })
|
|
84
|
+
|
|
85
|
+
// Hapus curve berdasarkan id
|
|
86
|
+
engine.removeCurve('c1')
|
|
87
|
+
|
|
88
|
+
// Baca semua curves
|
|
89
|
+
engine.getCurves() // → Curve[]
|
|
90
|
+
|
|
91
|
+
// Dapatkan computed bezier untuk rendering
|
|
92
|
+
engine.getCurveBeziers() // → Map<string, BezierCurve>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> **Catatan:** Curve diidentifikasi via `id` (string). Jika tidak diberikan saat `addCurve`, id otomatis di-generate.
|
|
96
|
+
|
|
97
|
+
> **Konsekuensi `removeLine`:** Curve yang terhubung ke line yang dihapus otomatis ikut terhapus. `removeLine` mengembalikan `{ success, removedCurveIds }`.
|
|
98
|
+
|
|
99
|
+
> **Path validation:** Gunakan `engine.canReach(fromLineId, fromOffset, toLineId, toOffset)` untuk mengecek apakah path ada tanpa membuat execution plan.
|
|
100
|
+
|
|
101
|
+
> **Acceleration:** Gunakan `engine.moveVehicleWithAcceleration(state, exec, accelState, config, deltaTime)` untuk movement dengan physics-based acceleration/deceleration.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Muat Scene Sekaligus
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Replace seluruh scene secara atomik
|
|
109
|
+
engine.setScene(lines, curves)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Vehicle
|
|
115
|
+
|
|
116
|
+
Library menyediakan tipe dasar `VehicleDefinition`. Client bebas extend dengan field tambahan (id, name, color, dsb).
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import type { VehicleDefinition } from 'vehicle-path2/core'
|
|
120
|
+
|
|
121
|
+
// Definisi minimal
|
|
122
|
+
const def: VehicleDefinition = { axleSpacings: [40] } // 2 axle, jarak 40px
|
|
123
|
+
|
|
124
|
+
// Client extend sesuai kebutuhan
|
|
125
|
+
interface MyVehicle extends VehicleDefinition {
|
|
126
|
+
id: string
|
|
127
|
+
name: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const truck: MyVehicle = { id: 'v1', name: 'Truck A', axleSpacings: [40, 40] } // 3 axle
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> `axleSpacings[i]` = jarak arc-length antara `axles[i]` dan `axles[i+1]`. Array harus memiliki minimal 1 entry.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Pergerakan Kendaraan
|
|
138
|
+
|
|
139
|
+
Tiga method berikut digunakan secara berurutan:
|
|
140
|
+
|
|
141
|
+
### 1. `initializeVehicle` — Tempatkan kendaraan
|
|
142
|
+
|
|
143
|
+
Hitung posisi awal semua axle di atas sebuah line.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const state = engine.initializeVehicle(
|
|
147
|
+
'L1', // lineId: line tempat kendaraan ditempatkan
|
|
148
|
+
0, // rearOffset: jarak dari start line ke axle paling belakang (px)
|
|
149
|
+
truck // VehicleDefinition (atau object yang extends-nya)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// state → VehiclePathState | null (null jika lineId tidak ditemukan)
|
|
153
|
+
// state.axles[0] = axle terdepan
|
|
154
|
+
// state.axles[N-1] = axle paling belakang (titik acuan)
|
|
155
|
+
// state.axleSpacings = [40, 40]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 2. `preparePath` — Tentukan tujuan
|
|
159
|
+
|
|
160
|
+
Jalankan Dijkstra untuk mencari rute dari posisi saat ini ke tujuan. Dipanggil **sekali** sebelum animasi dimulai.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const execution = engine.preparePath(
|
|
164
|
+
state, // posisi vehicle sekarang (dari initializeVehicle atau tick sebelumnya)
|
|
165
|
+
'L3', // targetLineId: line tujuan
|
|
166
|
+
0.5, // targetOffset: posisi di line tujuan
|
|
167
|
+
true // isPercentage: true = 50% dari panjang line, false = nilai absolut (px)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
// execution → PathExecution | null (null jika tidak ada rute)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 3. `moveVehicle` — Jalankan per tick
|
|
174
|
+
|
|
175
|
+
Dipanggil setiap frame di animation loop. Mengembalikan posisi baru semua axle.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const result = engine.moveVehicle(
|
|
179
|
+
state, // posisi vehicle sekarang
|
|
180
|
+
execution, // rencana rute (dari preparePath)
|
|
181
|
+
speed * deltaTime // jarak yang ditempuh di frame ini (px)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
state = result.state // posisi baru → simpan untuk tick berikutnya
|
|
185
|
+
execution = result.execution // progress terbaru → simpan untuk tick berikutnya
|
|
186
|
+
|
|
187
|
+
if (result.arrived) {
|
|
188
|
+
// Kendaraan sudah sampai tujuan
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### Contoh Lengkap: Animation Loop
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { PathEngine } from 'vehicle-path2/core'
|
|
198
|
+
import type { VehiclePathState, PathExecution } from 'vehicle-path2/core'
|
|
199
|
+
|
|
200
|
+
const engine = new PathEngine({ maxWheelbase: 100, tangentMode: 'proportional-40' })
|
|
201
|
+
|
|
202
|
+
engine.setScene(
|
|
203
|
+
[
|
|
204
|
+
{ id: 'L1', start: { x: 0, y: 0 }, end: { x: 400, y: 0 } },
|
|
205
|
+
{ id: 'L2', start: { x: 400, y: 0 }, end: { x: 400, y: 300 } },
|
|
206
|
+
],
|
|
207
|
+
[
|
|
208
|
+
{ fromLineId: 'L1', toLineId: 'L2', fromOffset: 1.0, fromIsPercentage: true, toOffset: 0.0, toIsPercentage: true }
|
|
209
|
+
]
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
// Client mengelola data vehicle sendiri
|
|
213
|
+
const myVehicle = { id: 'v1', name: 'Truck', axleSpacings: [40] }
|
|
214
|
+
|
|
215
|
+
// 1. Tempatkan di L1, axle belakang di posisi 0
|
|
216
|
+
let state: VehiclePathState = engine.initializeVehicle('L1', 0, myVehicle)!
|
|
217
|
+
|
|
218
|
+
// 2. Tentukan tujuan: ujung L2
|
|
219
|
+
let execution: PathExecution = engine.preparePath(state, 'L2', 1.0, true)!
|
|
220
|
+
|
|
221
|
+
const speed = 80 // px/detik
|
|
222
|
+
let lastTime = performance.now()
|
|
223
|
+
|
|
224
|
+
function animate() {
|
|
225
|
+
const now = performance.now()
|
|
226
|
+
const deltaTime = (now - lastTime) / 1000 // dalam detik
|
|
227
|
+
lastTime = now
|
|
228
|
+
|
|
229
|
+
// 3. Gerakkan vehicle setiap frame
|
|
230
|
+
const result = engine.moveVehicle(state, execution, speed * deltaTime)
|
|
231
|
+
state = result.state
|
|
232
|
+
execution = result.execution
|
|
233
|
+
|
|
234
|
+
// Render semua axle
|
|
235
|
+
for (const axle of state.axles) {
|
|
236
|
+
drawCircle(axle.position.x, axle.position.y)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!result.arrived) {
|
|
240
|
+
requestAnimationFrame(animate)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
requestAnimationFrame(animate)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Serialisasi Scene
|
|
250
|
+
|
|
251
|
+
Hanya lines dan curves yang di-serialize oleh library. **Vehicle tidak termasuk** — persistensi vehicle adalah tanggung jawab client.
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { serializeScene, deserializeScene } from 'vehicle-path2/core'
|
|
255
|
+
|
|
256
|
+
// Simpan scene ke JSON string
|
|
257
|
+
const json = serializeScene(engine.lines, engine.getCurves())
|
|
258
|
+
|
|
259
|
+
// Muat kembali
|
|
260
|
+
const snapshot = deserializeScene(json)
|
|
261
|
+
engine.setScene(snapshot.lines, snapshot.curves)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Geometry Utilities
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { projectPointOnLine, getValidRearOffsetRange, computeMinLineLength } from 'vehicle-path2/core'
|
|
270
|
+
|
|
271
|
+
// Proyeksikan mouse/pointer ke garis — berguna untuk hit detection
|
|
272
|
+
const { offset, distance } = projectPointOnLine({ x: 150, y: 10 }, line)
|
|
273
|
+
// offset = jarak dari start line ke titik proyeksi (px)
|
|
274
|
+
// distance = jarak tegak lurus dari point ke garis (px)
|
|
275
|
+
|
|
276
|
+
// Hitung range offset yang valid untuk axle belakang agar semua axle muat di line
|
|
277
|
+
const [min, max] = getValidRearOffsetRange(line, myVehicle.axleSpacings)
|
|
278
|
+
|
|
279
|
+
// Hitung panjang minimum sebuah line agar semua curve offset-nya tidak keluar batas
|
|
280
|
+
const minLen = computeMinLineLength('L1', engine.getCurves())
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
|
@@ -2,11 +2,13 @@ import type { Line, Curve, BezierCurve } from '../types/geometry';
|
|
|
2
2
|
import type { MovementConfig } from '../types/movement';
|
|
3
3
|
export interface GraphEdge {
|
|
4
4
|
curveIndex: number;
|
|
5
|
+
curveId?: string;
|
|
5
6
|
fromLineId: string;
|
|
6
7
|
toLineId: string;
|
|
7
8
|
fromOffset: number;
|
|
8
9
|
toOffset: number;
|
|
9
10
|
curveLength: number;
|
|
11
|
+
bezier: BezierCurve;
|
|
10
12
|
}
|
|
11
13
|
export interface Graph {
|
|
12
14
|
adjacency: Map<string, GraphEdge[]>;
|