vehicle-path2 1.0.0
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 +160 -0
- package/dist/animation-loop-bZEm2pMN.js +37 -0
- package/dist/animation-loop-fC2LjxCd.cjs +1 -0
- package/dist/core/algorithms/math.d.ts +35 -0
- package/dist/core/algorithms/pathFinding.d.ts +62 -0
- package/dist/core/algorithms/vehicleMovement.d.ts +174 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/types/api.d.ts +185 -0
- package/dist/core/types/config.d.ts +9 -0
- package/dist/core/types/geometry.d.ts +26 -0
- package/dist/core/types/movement.d.ts +56 -0
- package/dist/core/types/vehicle.d.ts +65 -0
- package/dist/core.cjs +1 -0
- package/dist/core.js +551 -0
- package/dist/index.d.ts +45 -0
- package/dist/react/dsl-hooks/useInitialMovement.d.ts +24 -0
- package/dist/react/dsl-hooks/useMovementSequence.d.ts +27 -0
- package/dist/react/dsl-hooks/useSceneDefinition.d.ts +22 -0
- package/dist/react/hooks/useAnimation.d.ts +43 -0
- package/dist/react/hooks/useMovementQueue.d.ts +52 -0
- package/dist/react/hooks/useScene.d.ts +78 -0
- package/dist/react/hooks/useVehicleSimulation.d.ts +126 -0
- package/dist/react/hooks/useVehicles.d.ts +55 -0
- package/dist/react/index.d.ts +48 -0
- package/dist/react/providers/useVehicleEvents.d.ts +78 -0
- package/dist/react.cjs +1 -0
- package/dist/react.js +18 -0
- package/dist/useVehicleEvents-B2JQFNjc.js +923 -0
- package/dist/useVehicleEvents-CBymulau.cjs +3 -0
- package/dist/utils/animation-loop.d.ts +105 -0
- package/dist/utils/dsl-parser.d.ts +152 -0
- package/dist/utils/event-emitter.d.ts +94 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/type-converters.d.ts +38 -0
- package/dist/utils/vehicle-helpers.d.ts +8 -0
- package/dist/utils.cjs +1 -0
- package/dist/utils.js +17 -0
- package/dist/vehicle-helpers-DIcksrtO.cjs +7 -0
- package/dist/vehicle-helpers-_72KxCqO.js +276 -0
- package/dist/vehicle-path.cjs +1 -0
- package/dist/vehicle-path.js +62 -0
- package/package.json +103 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# vehicle-path2
|
|
2
|
+
|
|
3
|
+
Library untuk simulasi pergerakan kendaraan dual-axle sepanjang jalur.
|
|
4
|
+
|
|
5
|
+
## Instalasi
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install vehicle-path2
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { useVehicleSimulation } from 'vehicle-path2/react'
|
|
15
|
+
|
|
16
|
+
function App() {
|
|
17
|
+
const sim = useVehicleSimulation({ wheelbase: 30 })
|
|
18
|
+
|
|
19
|
+
// Buat jalur
|
|
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')
|
|
23
|
+
|
|
24
|
+
// Tambah kendaraan
|
|
25
|
+
sim.addVehicles({ id: 'v1', lineId: 'line1', position: 0 })
|
|
26
|
+
|
|
27
|
+
// Gerakkan ke tujuan
|
|
28
|
+
sim.goto({ id: 'v1', lineId: 'line2', position: 1.0 })
|
|
29
|
+
|
|
30
|
+
// Jalankan animasi
|
|
31
|
+
sim.prepare()
|
|
32
|
+
sim.tick(5) // panggil di animation loop
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### Setup
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const sim = useVehicleSimulation({
|
|
42
|
+
wheelbase: 30, // jarak antar roda
|
|
43
|
+
tangentMode: 'proportional-40' // mode kurva (opsional)
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Scene
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
sim.addLine({ id: 'line1', start: [0, 0], end: [400, 0] })
|
|
51
|
+
sim.updateLine('line1', { end: [500, 100] })
|
|
52
|
+
sim.removeLine('line1')
|
|
53
|
+
sim.clearScene()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Koneksi
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
sim.connect('line1', 'line2')
|
|
60
|
+
sim.connect('line1', 'line2', { fromOffset: 0.8, toOffset: 0.2 })
|
|
61
|
+
sim.connect('line1', 'line2', { fromOffset: 150, fromIsPercentage: false, toOffset: 50, toIsPercentage: false })
|
|
62
|
+
sim.updateConnection('line1', 'line2', { fromOffset: 0.5 }) // update offset
|
|
63
|
+
sim.updateConnection('line1', 'line2', { toOffset: 100, toIsPercentage: false }) // absolute
|
|
64
|
+
sim.disconnect('line1', 'line2')
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Kendaraan
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
sim.addVehicles({ id: 'v1', lineId: 'line1', position: 0 })
|
|
71
|
+
sim.addVehicles({ id: 'v2', lineId: 'line1', position: 150, isPercentage: false }) // absolute
|
|
72
|
+
sim.updateVehicle('v1', { position: 0.5 }) // pindah ke 50%
|
|
73
|
+
sim.updateVehicle('v1', { lineId: 'line2' }) // pindah ke line lain
|
|
74
|
+
sim.updateVehicle('v1', { lineId: 'line2', position: 0.8 }) // pindah ke 80% di line2
|
|
75
|
+
sim.removeVehicle('v1')
|
|
76
|
+
sim.clearVehicles()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Pergerakan
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
sim.goto({ id: 'v1', lineId: 'line2' }) // default position = 1.0 (ujung)
|
|
83
|
+
sim.goto({ id: 'v1', lineId: 'line2', position: 0.5 }) // 0.5 = tengah line
|
|
84
|
+
sim.goto({ id: 'v1', lineId: 'line2', position: 150, isPercentage: false }) // absolute
|
|
85
|
+
sim.goto({ id: 'v1', lineId: 'line2', position: 0.5, wait: true }) // berhenti di tujuan
|
|
86
|
+
sim.goto({ id: 'v1', lineId: 'line2', payload: { orderId: '123' } }) // dengan payload
|
|
87
|
+
sim.clearQueue('v1')
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Animasi
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
sim.prepare() // siapkan sebelum animasi
|
|
94
|
+
sim.tick(5) // gerakkan 5 pixel per tick
|
|
95
|
+
sim.reset() // kembali ke posisi awal
|
|
96
|
+
sim.isMoving() // cek ada yang bergerak
|
|
97
|
+
sim.continueVehicle('v1') // lanjutkan vehicle yang wait
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Load dari DSL
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
sim.loadFromDSL(`
|
|
104
|
+
line1 : (0, 0) -> (400, 0)
|
|
105
|
+
line2 : (400, 0) -> (400, 300)
|
|
106
|
+
line1 -> line2
|
|
107
|
+
`)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### State
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
sim.lines // semua line
|
|
114
|
+
sim.curves // semua koneksi
|
|
115
|
+
sim.vehicles // kendaraan (posisi awal)
|
|
116
|
+
sim.movingVehicles // kendaraan (posisi saat animasi)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Contoh Lengkap
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { useVehicleSimulation } from 'vehicle-path2/react'
|
|
123
|
+
import { useEffect } from 'react'
|
|
124
|
+
|
|
125
|
+
function AnimatedVehicle() {
|
|
126
|
+
const sim = useVehicleSimulation({ wheelbase: 30 })
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
sim.addLine({ id: 'line1', start: [100, 100], end: [500, 100] })
|
|
130
|
+
sim.addVehicles({ id: 'v1', lineId: 'line1', position: 0 })
|
|
131
|
+
sim.goto({ id: 'v1', lineId: 'line1', position: 1.0 })
|
|
132
|
+
sim.prepare()
|
|
133
|
+
|
|
134
|
+
const animate = () => {
|
|
135
|
+
if (sim.tick(3)) {
|
|
136
|
+
requestAnimationFrame(animate)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
requestAnimationFrame(animate)
|
|
140
|
+
}, [])
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<svg width={600} height={200}>
|
|
144
|
+
{sim.movingVehicles.map(v => (
|
|
145
|
+
<circle
|
|
146
|
+
key={v.id}
|
|
147
|
+
cx={v.rear.position.x}
|
|
148
|
+
cy={v.rear.position.y}
|
|
149
|
+
r={10}
|
|
150
|
+
fill="blue"
|
|
151
|
+
/>
|
|
152
|
+
))}
|
|
153
|
+
</svg>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useRef as p, useEffect as d } from "react";
|
|
2
|
+
function A(o) {
|
|
3
|
+
const { onTick: t, onComplete: a, onStart: i, onPause: c, onStop: f } = o;
|
|
4
|
+
let n = null, u = null, e = !1, l = !1;
|
|
5
|
+
const s = (r) => {
|
|
6
|
+
if (!e) return;
|
|
7
|
+
const m = u !== null ? r - u : 0;
|
|
8
|
+
if (u = r, t(m) === !1) {
|
|
9
|
+
e = !1, n = null, u = null, a?.();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
n = requestAnimationFrame(s);
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
start: () => {
|
|
16
|
+
e || (e = !0, l = !1, u = null, i?.(), n = requestAnimationFrame(s));
|
|
17
|
+
},
|
|
18
|
+
pause: () => {
|
|
19
|
+
!e || l || (e = !1, l = !0, n !== null && (cancelAnimationFrame(n), n = null), c?.());
|
|
20
|
+
},
|
|
21
|
+
stop: () => {
|
|
22
|
+
e = !1, l = !1, u = null, n !== null && (cancelAnimationFrame(n), n = null), f?.();
|
|
23
|
+
},
|
|
24
|
+
isRunning: () => e,
|
|
25
|
+
isPaused: () => l
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function q(o) {
|
|
29
|
+
const t = p(null);
|
|
30
|
+
return t.current || (t.current = A(o)), d(() => () => {
|
|
31
|
+
t.current?.stop();
|
|
32
|
+
}, []), t.current;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
A as c,
|
|
36
|
+
q as u
|
|
37
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const i=require("react");function a(s){const{onTick:t,onComplete:c,onStart:f,onPause:m,onStop:p}=s;let n=null,o=null,e=!1,u=!1;const l=r=>{if(!e)return;const A=o!==null?r-o:0;if(o=r,t(A)===!1){e=!1,n=null,o=null,c?.();return}n=requestAnimationFrame(l)};return{start:()=>{e||(e=!0,u=!1,o=null,f?.(),n=requestAnimationFrame(l))},pause:()=>{!e||u||(e=!1,u=!0,n!==null&&(cancelAnimationFrame(n),n=null),m?.())},stop:()=>{e=!1,u=!1,o=null,n!==null&&(cancelAnimationFrame(n),n=null),p?.()},isRunning:()=>e,isPaused:()=>u}}function d(s){const t=i.useRef(null);return t.current||(t.current=a(s)),i.useEffect(()=>()=>{t.current?.stop()},[]),t.current}exports.createAnimationLoop=a;exports.useAnimationLoop=d;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Point, Line, BezierCurve } from '../types/geometry';
|
|
2
|
+
import type { TangentMode } from '../types/config';
|
|
3
|
+
import type { MovementConfig } from '../types/movement';
|
|
4
|
+
export declare function distance(p1: Point, p2: Point): number;
|
|
5
|
+
export declare function normalize(p1: Point, p2: Point): Point;
|
|
6
|
+
export declare function calculateTangentLength(mode: TangentMode, distance: number): number;
|
|
7
|
+
export interface CurveOffsetOptions {
|
|
8
|
+
fromOffset?: number;
|
|
9
|
+
fromIsPercentage?: boolean;
|
|
10
|
+
toOffset?: number;
|
|
11
|
+
toIsPercentage?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function createBezierCurve(line: Line, nextLine: Line, config: MovementConfig, willFlip?: boolean, offsetOptions?: CurveOffsetOptions): BezierCurve;
|
|
14
|
+
export declare function getPointOnLine(line: Line, t: number): Point;
|
|
15
|
+
export declare function getPointOnLineByOffset(line: Line, offset: number, isPercentage: boolean): Point;
|
|
16
|
+
export declare function getPointOnBezier(bezier: BezierCurve, t: number): Point;
|
|
17
|
+
export declare function isPointNearPoint(p1: Point, p2: Point, threshold?: number): boolean;
|
|
18
|
+
export interface ArcLengthEntry {
|
|
19
|
+
t: number;
|
|
20
|
+
distance: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a lookup table for arc-length parameterization.
|
|
24
|
+
* Maps t values to cumulative distances along the curve.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildArcLengthTable(bezier: BezierCurve, samples?: number): ArcLengthEntry[];
|
|
27
|
+
/**
|
|
28
|
+
* Convert a distance along the curve to the corresponding t parameter.
|
|
29
|
+
* Uses linear interpolation between table entries for smooth results.
|
|
30
|
+
*/
|
|
31
|
+
export declare function distanceToT(table: ArcLengthEntry[], targetDistance: number): number;
|
|
32
|
+
/**
|
|
33
|
+
* Get the total arc length from a pre-built table
|
|
34
|
+
*/
|
|
35
|
+
export declare function getArcLength(table: ArcLengthEntry[]): number;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Line, Curve, BezierCurve } from '../types/geometry';
|
|
2
|
+
import type { MovementConfig } from '../types/movement';
|
|
3
|
+
export interface GraphEdge {
|
|
4
|
+
curveIndex: number;
|
|
5
|
+
fromLineId: string;
|
|
6
|
+
toLineId: string;
|
|
7
|
+
fromOffset: number;
|
|
8
|
+
toOffset: number;
|
|
9
|
+
curveLength: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Graph {
|
|
12
|
+
adjacency: Map<string, GraphEdge[]>;
|
|
13
|
+
lines: Map<string, Line>;
|
|
14
|
+
lineLengths: Map<string, number>;
|
|
15
|
+
}
|
|
16
|
+
export interface PathSegment {
|
|
17
|
+
type: 'line' | 'curve';
|
|
18
|
+
lineId?: string;
|
|
19
|
+
curveIndex?: number;
|
|
20
|
+
startOffset: number;
|
|
21
|
+
endOffset: number;
|
|
22
|
+
length: number;
|
|
23
|
+
}
|
|
24
|
+
export interface PathResult {
|
|
25
|
+
segments: PathSegment[];
|
|
26
|
+
totalDistance: number;
|
|
27
|
+
}
|
|
28
|
+
export interface VehiclePosition {
|
|
29
|
+
lineId: string;
|
|
30
|
+
offset: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Menghitung arc length dari bezier curve menggunakan numerical integration
|
|
34
|
+
*/
|
|
35
|
+
export declare function calculateBezierArcLength(bezier: BezierCurve, segments?: number): number;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve offset untuk FROM line (garis asal kurva)
|
|
38
|
+
* - 0% → wheelbase (bukan 0, untuk memberi ruang vehicle)
|
|
39
|
+
* - 100% → lineLength (ujung garis)
|
|
40
|
+
*
|
|
41
|
+
* Effective range: [wheelbase, lineLength]
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveFromLineOffset(line: Line, offset: number | undefined, isPercentage: boolean | undefined, defaultPercentage: number, wheelbase: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve offset untuk TO line (garis tujuan kurva)
|
|
46
|
+
* - 0% → 0 (awal garis)
|
|
47
|
+
* - 100% → lineLength - wheelbase (untuk memberi ruang vehicle)
|
|
48
|
+
*
|
|
49
|
+
* Effective range: [0, lineLength - wheelbase]
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveToLineOffset(line: Line, offset: number | undefined, isPercentage: boolean | undefined, defaultPercentage: number, wheelbase: number): number;
|
|
52
|
+
/**
|
|
53
|
+
* Membangun graph dari lines dan curves
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildGraph(lines: Line[], curves: Curve[], config: MovementConfig): Graph;
|
|
56
|
+
/**
|
|
57
|
+
* Mencari path terpendek dari posisi vehicle ke target
|
|
58
|
+
*
|
|
59
|
+
* @returns PathResult jika path ditemukan, null jika tidak ada path valid
|
|
60
|
+
*/
|
|
61
|
+
export declare function findPath(graph: Graph, vehiclePos: VehiclePosition, targetLineId: string, targetOffset: number, // Absolute offset
|
|
62
|
+
targetIsPercentage?: boolean): PathResult | null;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vehicle Movement Module
|
|
3
|
+
*
|
|
4
|
+
* Handles dual-axle vehicle movement including:
|
|
5
|
+
* - Position calculations (line and curve)
|
|
6
|
+
* - Arc-length tracking for wheelbase
|
|
7
|
+
* - Path preparation and execution
|
|
8
|
+
* - Segment transitions
|
|
9
|
+
*/
|
|
10
|
+
import type { Line, Point, Curve } from '../types/geometry';
|
|
11
|
+
import type { Vehicle, AxleState, GotoCommand, GotoCompletionCallback } from '../types/vehicle';
|
|
12
|
+
import type { VehicleMovementState, PathExecutionState, AxleExecutionState, CurveData, SceneContext, MovementConfig } from '../types/movement';
|
|
13
|
+
import type { PathResult } from './pathFinding';
|
|
14
|
+
import type { CommandStartInfo } from '../../utils/event-emitter';
|
|
15
|
+
export type { CurveData, PathExecutionState, VehicleMovementState } from '../types/movement';
|
|
16
|
+
/**
|
|
17
|
+
* Get position on line from absolute offset
|
|
18
|
+
* This is a pure function that converts absolute offset to Point coordinates
|
|
19
|
+
*/
|
|
20
|
+
export declare function getPositionFromOffset(line: Line, absoluteOffset: number): Point;
|
|
21
|
+
/**
|
|
22
|
+
* Calculate line length
|
|
23
|
+
*/
|
|
24
|
+
export declare function getLineLength(line: Line): number;
|
|
25
|
+
/**
|
|
26
|
+
* Calculate cumulative arc-length from start of path
|
|
27
|
+
*
|
|
28
|
+
* @param path - The path being followed
|
|
29
|
+
* @param segmentIndex - Current segment index
|
|
30
|
+
* @param segmentDistance - Distance along current segment
|
|
31
|
+
* @returns Total arc-length from path start
|
|
32
|
+
*/
|
|
33
|
+
export declare function getCumulativeArcLength(path: PathResult, segmentIndex: number, segmentDistance: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* Convert arc-length to segment position
|
|
36
|
+
*
|
|
37
|
+
* @param path - The path being followed
|
|
38
|
+
* @param targetArcLength - Target cumulative arc-length
|
|
39
|
+
* @returns Segment position or null if exceeds path length
|
|
40
|
+
*/
|
|
41
|
+
export declare function arcLengthToSegmentPosition(path: PathResult, targetArcLength: number): {
|
|
42
|
+
segmentIndex: number;
|
|
43
|
+
segmentDistance: number;
|
|
44
|
+
} | null;
|
|
45
|
+
/**
|
|
46
|
+
* Calculate front axle position from rear position + wheelbase
|
|
47
|
+
*
|
|
48
|
+
* @param path - The path being followed
|
|
49
|
+
* @param rearSegmentIndex - Rear axle segment index
|
|
50
|
+
* @param rearSegmentDistance - Rear axle distance in segment
|
|
51
|
+
* @param wheelbase - Distance between front and rear axles
|
|
52
|
+
* @returns Front axle position or null if exceeds path
|
|
53
|
+
*/
|
|
54
|
+
export declare function calculateFrontAxlePosition(path: PathResult, rearSegmentIndex: number, rearSegmentDistance: number, wheelbase: number): {
|
|
55
|
+
segmentIndex: number;
|
|
56
|
+
segmentDistance: number;
|
|
57
|
+
} | null;
|
|
58
|
+
/**
|
|
59
|
+
* Calculate initial front axle position from rear position
|
|
60
|
+
*
|
|
61
|
+
* @param rLineId - Rear axle line ID
|
|
62
|
+
* @param rAbsoluteOffset - Rear axle absolute offset on line
|
|
63
|
+
* @param wheelbase - Distance between front and rear axles
|
|
64
|
+
* @param line - The line the vehicle is on
|
|
65
|
+
* @returns Front axle state
|
|
66
|
+
*/
|
|
67
|
+
export declare function calculateInitialFrontPosition(rLineId: string, rAbsoluteOffset: number, wheelbase: number, line: Line): AxleState;
|
|
68
|
+
/**
|
|
69
|
+
* Initialize Vehicle with runtime state for dual-axle
|
|
70
|
+
*
|
|
71
|
+
* @param vehicle - Vehicle to initialize (must have rear and front already populated)
|
|
72
|
+
* @param _line - The line (kept for compatibility)
|
|
73
|
+
* @returns Vehicle with state set to idle
|
|
74
|
+
*/
|
|
75
|
+
export declare function initializeMovingVehicle(vehicle: Vehicle, _line: Line): Vehicle;
|
|
76
|
+
/**
|
|
77
|
+
* Create initial movement state for a vehicle
|
|
78
|
+
*/
|
|
79
|
+
export declare function createInitialMovementState(vehicle: Vehicle): VehicleMovementState;
|
|
80
|
+
export interface InitializationResult {
|
|
81
|
+
movingVehicles: Vehicle[];
|
|
82
|
+
stateMap: Map<string, VehicleMovementState>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Initialize all vehicles and their movement states
|
|
86
|
+
*
|
|
87
|
+
* @param vehicles - Array of vehicles to initialize
|
|
88
|
+
* @param linesMap - Map of line IDs to Line objects
|
|
89
|
+
* @returns InitializationResult with moving vehicles and state map
|
|
90
|
+
*/
|
|
91
|
+
export declare function initializeAllVehicles(vehicles: Vehicle[], linesMap: Map<string, Line>): InitializationResult;
|
|
92
|
+
/**
|
|
93
|
+
* Calculate position on line (pure function)
|
|
94
|
+
*
|
|
95
|
+
* @param line - The line to calculate position on
|
|
96
|
+
* @param absoluteOffset - The offset along the line
|
|
97
|
+
* @returns Position data
|
|
98
|
+
*/
|
|
99
|
+
export declare function calculatePositionOnLine(line: Line, absoluteOffset: number): {
|
|
100
|
+
position: Point;
|
|
101
|
+
lineId: string;
|
|
102
|
+
absoluteOffset: number;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Calculate position on curve (pure function)
|
|
106
|
+
*
|
|
107
|
+
* @param curveData - The curve data with arc-length table
|
|
108
|
+
* @param segmentDistance - Distance along the curve
|
|
109
|
+
* @returns Position data
|
|
110
|
+
*/
|
|
111
|
+
export declare function calculatePositionOnCurve(curveData: CurveData, segmentDistance: number): {
|
|
112
|
+
position: Point;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Update single axle position and handle segment transitions
|
|
116
|
+
*
|
|
117
|
+
* @param axleState - Current axle state
|
|
118
|
+
* @param axleExecution - Current execution state for this axle
|
|
119
|
+
* @param path - The path being followed
|
|
120
|
+
* @param velocity - Distance to move this frame
|
|
121
|
+
* @param linesMap - Map of line IDs to Line objects
|
|
122
|
+
* @param curveDataMap - Map of curve indices to curve data
|
|
123
|
+
* @param maxOffset - Optional max offset for extending beyond path (for front axle)
|
|
124
|
+
* @returns Updated axle state, execution state, and completion flag
|
|
125
|
+
*/
|
|
126
|
+
export declare function updateAxlePosition(axleState: AxleState, axleExecution: AxleExecutionState, path: PathResult, velocity: number, linesMap: Map<string, Line>, curveDataMap: Map<number, CurveData>, maxOffset?: number): {
|
|
127
|
+
axleState: AxleState;
|
|
128
|
+
execution: AxleExecutionState;
|
|
129
|
+
completed: boolean;
|
|
130
|
+
};
|
|
131
|
+
export interface PreparedPath {
|
|
132
|
+
path: PathResult;
|
|
133
|
+
curveDataMap: Map<number, CurveData>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Prepare path and curve data for a goto command
|
|
137
|
+
* Returns null if no path found
|
|
138
|
+
*
|
|
139
|
+
* @param vehicle - The vehicle to move
|
|
140
|
+
* @param command - The goto command with target
|
|
141
|
+
* @param ctx - Scene context with graph, linesMap, curves, and config
|
|
142
|
+
* @returns PreparedPath with path and curve data, or null if no path found
|
|
143
|
+
*/
|
|
144
|
+
export declare function prepareCommandPath(vehicle: Vehicle, command: GotoCommand, ctx: SceneContext): PreparedPath | null;
|
|
145
|
+
type CommandStartCallback = (info: CommandStartInfo) => void;
|
|
146
|
+
export interface SegmentCompletionContext {
|
|
147
|
+
linesMap: Map<string, Line>;
|
|
148
|
+
config: MovementConfig;
|
|
149
|
+
vehicleQueues: Map<string, GotoCommand[]>;
|
|
150
|
+
curves: Curve[];
|
|
151
|
+
graphRef: {
|
|
152
|
+
current: ReturnType<typeof import('./pathFinding').buildGraph> | null;
|
|
153
|
+
};
|
|
154
|
+
prepareCommandPath: (vehicle: Vehicle, command: GotoCommand, ctx: SceneContext) => {
|
|
155
|
+
path: PathResult;
|
|
156
|
+
curveDataMap: Map<number, CurveData>;
|
|
157
|
+
} | null;
|
|
158
|
+
onCommandComplete?: GotoCompletionCallback;
|
|
159
|
+
onCommandStart?: CommandStartCallback;
|
|
160
|
+
}
|
|
161
|
+
export interface SegmentCompletionResult {
|
|
162
|
+
handled: boolean;
|
|
163
|
+
vehicle: Vehicle;
|
|
164
|
+
/** New execution state (null if completed or unchanged) */
|
|
165
|
+
newExecution?: PathExecutionState | null;
|
|
166
|
+
/** True if vehicle is waiting for confirmation to continue */
|
|
167
|
+
isWaiting?: boolean;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Handle arrival at destination - all segments completed (pure function)
|
|
171
|
+
* Checks for next command in queue and starts it, or marks vehicle as arrived
|
|
172
|
+
*/
|
|
173
|
+
export declare function handleArrival(state: VehicleMovementState, ctx: SegmentCompletionContext): SegmentCompletionResult;
|
|
174
|
+
export type { VehicleMovementState as SegmentVehicleState };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Layer - Pure algorithms and types
|
|
3
|
+
*
|
|
4
|
+
* This layer contains the essential logic without any framework dependencies.
|
|
5
|
+
* Use this if you want to implement your own animation loop or React integration.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { buildGraph, findPath, distance } from 'vehicle-path/core'
|
|
10
|
+
* import type { Point, Line, Vehicle } from 'vehicle-path/core'
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export type { Point, Line, BezierCurve, Curve } from './types/geometry';
|
|
14
|
+
export type { VehicleState, VehicleStart, Vehicle, AxleState, GotoCommand, GotoCompletionInfo, GotoCompletionCallback } from './types/vehicle';
|
|
15
|
+
export type { CurveData, PathExecutionState, VehicleMovementState, MovementConfig, SceneContext } from './types/movement';
|
|
16
|
+
export type { TangentMode } from './types/config';
|
|
17
|
+
export type { CoordinateInput, SceneLineInput, SceneConnectionInput, SceneConfig, VehicleInput, VehicleUpdateInput, ConnectionUpdateInput, GotoCommandInput } from './types/api';
|
|
18
|
+
export { buildGraph, findPath, calculateBezierArcLength, resolveFromLineOffset, resolveToLineOffset, type Graph, type GraphEdge, type PathSegment, type PathResult, type VehiclePosition } from './algorithms/pathFinding';
|
|
19
|
+
export { initializeMovingVehicle, createInitialMovementState, initializeAllVehicles, calculateInitialFrontPosition, type InitializationResult, updateAxlePosition, calculatePositionOnLine, calculatePositionOnCurve, calculateFrontAxlePosition, getCumulativeArcLength, arcLengthToSegmentPosition, prepareCommandPath, type PreparedPath, handleArrival, type SegmentCompletionContext, type SegmentCompletionResult, type SegmentVehicleState, getPositionFromOffset, getLineLength } from './algorithms/vehicleMovement';
|
|
20
|
+
export { distance, normalize, getPointOnLine, getPointOnLineByOffset, getPointOnBezier, createBezierCurve, buildArcLengthTable, distanceToT, getArcLength, calculateTangentLength, isPointNearPoint, type ArcLengthEntry, type CurveOffsetOptions } from './algorithms/math';
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Input Types
|
|
3
|
+
*
|
|
4
|
+
* These types define the simplified input format for the programmatic API.
|
|
5
|
+
* They are designed to be more ergonomic than the internal types.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Simple coordinate input - can be [x, y] tuple or {x, y} object
|
|
9
|
+
*/
|
|
10
|
+
export type CoordinateInput = [number, number] | {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Line definition for Scene API
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* { id: 'line001', start: [100, 100], end: [500, 100] }
|
|
19
|
+
* { id: 'line002', start: { x: 500, y: 100 }, end: { x: 500, y: 400 } }
|
|
20
|
+
*/
|
|
21
|
+
export interface SceneLineInput {
|
|
22
|
+
id: string;
|
|
23
|
+
start: CoordinateInput;
|
|
24
|
+
end: CoordinateInput;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Connection (curve) definition for Scene API
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* { from: 'line001', to: 'line002' }
|
|
31
|
+
* { from: 'line001', fromPosition: 0.8, to: 'line002', toPosition: 0.2 }
|
|
32
|
+
* { from: 'line001', fromPosition: 150, fromIsPercentage: false, to: 'line002', toPosition: 50, toIsPercentage: false }
|
|
33
|
+
*/
|
|
34
|
+
export interface SceneConnectionInput {
|
|
35
|
+
from: string;
|
|
36
|
+
fromPosition?: number;
|
|
37
|
+
fromIsPercentage?: boolean;
|
|
38
|
+
to: string;
|
|
39
|
+
toPosition?: number;
|
|
40
|
+
toIsPercentage?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Full scene configuration for setScene()
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* setScene({
|
|
47
|
+
* lines: [
|
|
48
|
+
* { id: 'line001', start: [100, 100], end: [500, 100] },
|
|
49
|
+
* { id: 'line002', start: [500, 100], end: [500, 400] }
|
|
50
|
+
* ],
|
|
51
|
+
* connections: [
|
|
52
|
+
* { from: 'line001', to: 'line002' }
|
|
53
|
+
* ]
|
|
54
|
+
* })
|
|
55
|
+
*/
|
|
56
|
+
export interface SceneConfig {
|
|
57
|
+
lines: SceneLineInput[];
|
|
58
|
+
connections?: SceneConnectionInput[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Vehicle creation input for addVehicle()
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* addVehicle({ id: 'v1', lineId: 'line001' }) // position defaults to 0 (start)
|
|
65
|
+
* addVehicle({ id: 'v2', lineId: 'line002', position: 0.5 }) // 50% of line
|
|
66
|
+
* addVehicle({ id: 'v3', lineId: 'line003', position: 150, isPercentage: false }) // absolute 150
|
|
67
|
+
*/
|
|
68
|
+
export interface VehicleInput {
|
|
69
|
+
id: string;
|
|
70
|
+
lineId: string;
|
|
71
|
+
position?: number;
|
|
72
|
+
isPercentage?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Vehicle update input for updateVehicle()
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* updateVehicle('v1', { lineId: 'line002' }) // move to different line (keeps position 0)
|
|
79
|
+
* updateVehicle('v1', { position: 0.5 }) // move to 50% on current line
|
|
80
|
+
* updateVehicle('v1', { lineId: 'line002', position: 0.8 }) // move to 80% on line002
|
|
81
|
+
* updateVehicle('v1', { position: 150, isPercentage: false }) // move to absolute 150
|
|
82
|
+
*/
|
|
83
|
+
export interface VehicleUpdateInput {
|
|
84
|
+
lineId?: string;
|
|
85
|
+
position?: number;
|
|
86
|
+
isPercentage?: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Connection update input for updateConnection()
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* updateConnection('line001', 'line002', { fromOffset: 0.8 }) // change from offset to 80%
|
|
93
|
+
* updateConnection('line001', 'line002', { toOffset: 0.2 }) // change to offset to 20%
|
|
94
|
+
* updateConnection('line001', 'line002', { fromOffset: 150, fromIsPercentage: false }) // absolute offset
|
|
95
|
+
*/
|
|
96
|
+
export interface ConnectionUpdateInput {
|
|
97
|
+
fromOffset?: number;
|
|
98
|
+
fromIsPercentage?: boolean;
|
|
99
|
+
toOffset?: number;
|
|
100
|
+
toIsPercentage?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Simplified goto input for useVehicleSimulation.goto()
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* goto({ id: 'v1', lineId: 'line002' }) // position defaults to 1.0 (end)
|
|
107
|
+
* goto({ id: 'v1', lineId: 'line002', position: 0.5 }) // 50% of line
|
|
108
|
+
* goto({ id: 'v1', lineId: 'line002', position: 150, isPercentage: false }) // absolute 150
|
|
109
|
+
* goto({ id: 'v1', lineId: 'line002', position: 0.5, wait: true }) // wait at destination
|
|
110
|
+
* goto({ id: 'v1', lineId: 'line002', payload: { orderId: '123' } }) // with payload
|
|
111
|
+
*/
|
|
112
|
+
export interface GotoInput {
|
|
113
|
+
id: string;
|
|
114
|
+
lineId: string;
|
|
115
|
+
position?: number;
|
|
116
|
+
isPercentage?: boolean;
|
|
117
|
+
wait?: boolean;
|
|
118
|
+
payload?: unknown;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Input for goto/queueMovement commands (API-friendly version of GotoCommand)
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* queueMovement('v1', { targetLineId: 'line002' }) // targetPosition defaults to 1.0 (end)
|
|
125
|
+
* queueMovement('v1', { targetLineId: 'line002', targetPosition: 0.5 }) // 50% of line
|
|
126
|
+
* queueMovement('v1', {
|
|
127
|
+
* targetLineId: 'line002',
|
|
128
|
+
* targetPosition: 0.5,
|
|
129
|
+
* wait: true,
|
|
130
|
+
* payload: { orderId: '123' }
|
|
131
|
+
* })
|
|
132
|
+
* queueMovement('v1', { targetLineId: 'line002', targetPosition: 150, isPercentage: false })
|
|
133
|
+
*/
|
|
134
|
+
export interface GotoCommandInput {
|
|
135
|
+
targetLineId: string;
|
|
136
|
+
targetPosition?: number;
|
|
137
|
+
isPercentage?: boolean;
|
|
138
|
+
wait?: boolean;
|
|
139
|
+
payload?: unknown;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Movement command input for SimulationConfig
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* { vehicleId: 'v1', targetLineId: 'line002' } // position defaults to 1.0 (end)
|
|
146
|
+
* { vehicleId: 'v1', targetLineId: 'line002', targetPosition: 0.5 } // 50% of line
|
|
147
|
+
* { vehicleId: 'v1', targetLineId: 'line002', targetPosition: 150, isPercentage: false }
|
|
148
|
+
*/
|
|
149
|
+
export interface MovementCommandInput {
|
|
150
|
+
vehicleId: string;
|
|
151
|
+
targetLineId: string;
|
|
152
|
+
targetPosition?: number;
|
|
153
|
+
isPercentage?: boolean;
|
|
154
|
+
wait?: boolean;
|
|
155
|
+
payload?: unknown;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Full simulation configuration for loadFromJSON()
|
|
159
|
+
*
|
|
160
|
+
* Allows loading an entire simulation state from a JSON object,
|
|
161
|
+
* including lines, connections, vehicles, and movement commands.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* loadFromJSON({
|
|
165
|
+
* lines: [
|
|
166
|
+
* { id: 'line001', start: [100, 100], end: [500, 100] },
|
|
167
|
+
* { id: 'line002', start: [500, 100], end: [500, 400] }
|
|
168
|
+
* ],
|
|
169
|
+
* connections: [
|
|
170
|
+
* { from: 'line001', to: 'line002' }
|
|
171
|
+
* ],
|
|
172
|
+
* vehicles: [
|
|
173
|
+
* { id: 'v1', lineId: 'line001', position: 0 }
|
|
174
|
+
* ],
|
|
175
|
+
* movements: [
|
|
176
|
+
* { vehicleId: 'v1', targetLineId: 'line002', targetPosition: 1.0 }
|
|
177
|
+
* ]
|
|
178
|
+
* })
|
|
179
|
+
*/
|
|
180
|
+
export interface SimulationConfig {
|
|
181
|
+
lines: SceneLineInput[];
|
|
182
|
+
connections?: SceneConnectionInput[];
|
|
183
|
+
vehicles?: VehicleInput[];
|
|
184
|
+
movements?: MovementCommandInput[];
|
|
185
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration types for algorithm behavior
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Tangent calculation mode for bezier curves
|
|
6
|
+
* - proportional-40: 40% of distance for tangent length
|
|
7
|
+
* - magic-55: 55.22% of distance (approximates circular arc)
|
|
8
|
+
*/
|
|
9
|
+
export type TangentMode = 'proportional-40' | 'magic-55';
|