sim-lidar-rs 0.1.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 ADDED
@@ -0,0 +1,55 @@
1
+ # sim-lidar-rs
2
+
3
+ High-performance simulated LiDAR sensor library built for the web.
4
+ Computes 100,000+ ray intersections per frame in Rust/WebAssembly, off the main thread.
5
+
6
+ ## Features
7
+
8
+ - ⚡ **Fast** – Bounding Volume Hierarchy (BVH) accelerator built in Rust, compiled to Wasm
9
+ - 🔒 **Non-blocking** – All computation runs inside a Web Worker
10
+ - 🎯 **Realistic** – Configurable vertical channels, FOV, range limits, and Gaussian noise
11
+ - 📦 **Zero-copy** – Hit-point buffers transferred directly from Wasm to JS
12
+ - 🔷 **Strictly typed** – Full TypeScript API with sensor presets (VLP-16, Ouster OS1-32/64)
13
+
14
+ ## Quick Start
15
+
16
+ ```ts
17
+ import { LidarClient, VLP16_CONFIG } from 'sim-lidar-rs';
18
+
19
+ const vertices = new Float32Array([/* x,y,z ... */]);
20
+ const indices = new Uint32Array([/* i0,i1,i2 ... */]);
21
+
22
+ const lidar = new LidarClient(vertices, indices, VLP16_CONFIG);
23
+ await lidar.ready();
24
+
25
+ const { hits, hitCount } = await lidar.scan({
26
+ position: { x: 0, y: 1.5, z: 0 },
27
+ });
28
+ console.log(`${hitCount} hits`); // hits is Float32Array [x,y,z, ...]
29
+
30
+ lidar.dispose();
31
+ ```
32
+
33
+ ## Build
34
+
35
+ ```bash
36
+ # Build Wasm module
37
+ wasm-pack build --target web --out-dir ts/wasm
38
+
39
+ # Build TypeScript library
40
+ npm run build
41
+
42
+ # Run Rust unit tests
43
+ cargo test
44
+
45
+ # Run TypeScript tests
46
+ npm test
47
+ ```
48
+
49
+ ## Documentation
50
+
51
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for the full architecture, core objectives, technical design, and sensor configuration reference.
52
+
53
+ ## License
54
+
55
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ /**
2
+ * sim-lidar-rs – Public TypeScript API
3
+ *
4
+ * Usage:
5
+ * ```ts
6
+ * import { LidarClient, VLP16_CONFIG } from 'sim-lidar-rs';
7
+ *
8
+ * const lidar = new LidarClient(vertices, indices, VLP16_CONFIG);
9
+ * await lidar.ready();
10
+ * const { hits, hitCount } = await lidar.scan({ position: { x: 0, y: 1, z: 0 } });
11
+ * ```
12
+ */
13
+ export type { SensorConfig, Pose, ScanResult, Geometry, SimLidarEventHandlers } from "./types.js";
14
+ export { VLP16_CONFIG, OUSTER_OS1_32_CONFIG, OUSTER_OS1_64_CONFIG, totalRays, SimLidarError, SimLidarNotInitializedError, SimLidarDisposedError, } from "./types.js";
15
+ import type { SensorConfig, Pose, ScanResult, Geometry, SimLidarEventHandlers } from "./types.js";
16
+ /**
17
+ * `LidarClient` wraps the Wasm LiDAR simulator running inside a Web Worker.
18
+ * All heavy computation runs off the main thread.
19
+ */
20
+ export declare class LidarClient {
21
+ private worker;
22
+ private pending;
23
+ private readyPromise;
24
+ private _scanCounter;
25
+ private _disposed;
26
+ private readonly _handlers;
27
+ constructor(vertices: Float32Array, indices: Uint32Array, config: SensorConfig,
28
+ /** Optional URL/path to the worker script. Defaults to the bundled worker. */
29
+ workerUrl?: string | URL,
30
+ /** Optional observability callbacks. */
31
+ handlers?: SimLidarEventHandlers);
32
+ /** Resolves once the Wasm module has been initialised and the BVH built. */
33
+ ready(): Promise<void>;
34
+ /** Update the sensor configuration without rebuilding the BVH. */
35
+ setConfig(config: SensorConfig): void;
36
+ /** Run a full scan and return the resulting point cloud. */
37
+ scan(pose: Pose): Promise<ScanResult>;
38
+ /** Terminate the underlying Web Worker, rejecting any in-flight scan Promises. */
39
+ dispose(): void;
40
+ private _onMessage;
41
+ }
42
+ /**
43
+ * `SimLidar` is the primary TypeScript API for the sim-lidar-rs library.
44
+ *
45
+ * It abstracts the Web Worker communication using Promises and provides
46
+ * explicit lifecycle management to ensure Wasm instances are freed when
47
+ * the simulator is no longer needed.
48
+ *
49
+ * ```ts
50
+ * const lidar = new SimLidar(VLP16_CONFIG);
51
+ * await lidar.init();
52
+ * await lidar.updateEnvironment({ vertices, indices });
53
+ * const hits = await lidar.scan({ position: { x: 0, y: 1, z: 0 } });
54
+ * // hits is a Float32Array [x,y,z, x,y,z, …]
55
+ * lidar.destroy();
56
+ * ```
57
+ */
58
+ export declare class SimLidar {
59
+ private worker;
60
+ private pending;
61
+ private _counter;
62
+ private readonly _config;
63
+ private _disposed;
64
+ private readonly _handlers;
65
+ constructor(config: SensorConfig,
66
+ /** Optional URL/path to the worker script. Defaults to the bundled worker. */
67
+ workerUrl?: string | URL,
68
+ /** Optional observability callbacks. */
69
+ handlers?: SimLidarEventHandlers);
70
+ /**
71
+ * Initialise the Wasm module inside the Web Worker.
72
+ * Must be called before {@link updateEnvironment} or {@link scan}.
73
+ */
74
+ init(): Promise<void>;
75
+ /**
76
+ * Load (or replace) the environment geometry and rebuild the BVH.
77
+ * Buffers are transferred to the worker to avoid copying.
78
+ */
79
+ updateEnvironment(geometry: Geometry): Promise<void>;
80
+ /**
81
+ * Run a full LiDAR scan from the given pose.
82
+ *
83
+ * @returns A `Promise` that resolves with a `Float32Array` of world-space
84
+ * hit coordinates `[x,y,z, x,y,z, …]`.
85
+ */
86
+ scan(pose: Pose): Promise<Float32Array>;
87
+ /**
88
+ * Destroy the simulator: asks the worker to explicitly free the Wasm
89
+ * instance (releasing linear memory) and then terminates the worker thread.
90
+ *
91
+ * Any in-flight Promises are rejected with a {@link SimLidarDisposedError}.
92
+ */
93
+ destroy(): void;
94
+ private _onMessage;
95
+ }
@@ -0,0 +1,237 @@
1
+ var g = Object.defineProperty;
2
+ var y = (o, t, e) => t in o ? g(o, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[t] = e;
3
+ var d = (o, t, e) => y(o, typeof t != "symbol" ? t + "" : t, e);
4
+ const f = {
5
+ horizontalResolution: 1800,
6
+ verticalChannels: 16,
7
+ verticalFovUpper: 15,
8
+ verticalFovLower: -15,
9
+ minRange: 0.1,
10
+ maxRange: 100,
11
+ noiseStddev: 0
12
+ }, w = {
13
+ horizontalResolution: 1024,
14
+ verticalChannels: 32,
15
+ verticalFovUpper: 22.5,
16
+ verticalFovLower: -22.5,
17
+ minRange: 0.1,
18
+ maxRange: 120,
19
+ noiseStddev: 0
20
+ }, m = {
21
+ horizontalResolution: 2048,
22
+ verticalChannels: 64,
23
+ verticalFovUpper: 22.5,
24
+ verticalFovLower: -22.5,
25
+ minRange: 0.1,
26
+ maxRange: 120,
27
+ noiseStddev: 0
28
+ };
29
+ function v(o) {
30
+ return o.horizontalResolution * o.verticalChannels;
31
+ }
32
+ class h extends Error {
33
+ constructor(t) {
34
+ super(t), this.name = "SimLidarError", Object.setPrototypeOf(this, new.target.prototype);
35
+ }
36
+ }
37
+ class k extends h {
38
+ constructor() {
39
+ super("Simulator not initialised. Await init() / ready() before calling scan()."), this.name = "SimLidarNotInitializedError", Object.setPrototypeOf(this, new.target.prototype);
40
+ }
41
+ }
42
+ class p extends h {
43
+ constructor() {
44
+ super("This SimLidar instance has been destroyed and can no longer be used."), this.name = "SimLidarDisposedError", Object.setPrototypeOf(this, new.target.prototype);
45
+ }
46
+ }
47
+ class C {
48
+ constructor(t, e, i, r, a) {
49
+ d(this, "worker");
50
+ d(this, "pending", /* @__PURE__ */ new Map());
51
+ d(this, "readyPromise");
52
+ d(this, "_scanCounter", 0);
53
+ d(this, "_disposed", !1);
54
+ d(this, "_handlers");
55
+ this._handlers = a ?? {};
56
+ const c = r ?? new URL("./worker.js", import.meta.url);
57
+ this.worker = new Worker(c, { type: "module" }), this.worker.addEventListener("message", this._onMessage.bind(this)), this.readyPromise = new Promise((_, l) => {
58
+ this.pending.set("__ready__", {
59
+ resolve: _,
60
+ reject: l
61
+ });
62
+ });
63
+ const s = new Float32Array(t), n = new Uint32Array(e);
64
+ this.worker.postMessage(
65
+ { type: "init", vertices: s, indices: n, config: i },
66
+ [s.buffer, n.buffer]
67
+ );
68
+ }
69
+ /** Resolves once the Wasm module has been initialised and the BVH built. */
70
+ ready() {
71
+ return this.readyPromise;
72
+ }
73
+ /** Update the sensor configuration without rebuilding the BVH. */
74
+ setConfig(t) {
75
+ if (this._disposed) throw new p();
76
+ this.worker.postMessage({ type: "setConfig", config: t });
77
+ }
78
+ /** Run a full scan and return the resulting point cloud. */
79
+ scan(t) {
80
+ return this._disposed ? Promise.reject(new p()) : new Promise((e, i) => {
81
+ const r = `scan_${++this._scanCounter}`;
82
+ this.pending.set(r, {
83
+ resolve: e,
84
+ reject: i
85
+ }), this.worker.postMessage({ type: "scan", pose: t, __id: r });
86
+ });
87
+ }
88
+ /** Terminate the underlying Web Worker, rejecting any in-flight scan Promises. */
89
+ dispose() {
90
+ if (this._disposed) return;
91
+ this._disposed = !0;
92
+ const t = new p();
93
+ for (const e of this.pending.values())
94
+ e.reject(t);
95
+ this.pending.clear(), this.worker.terminate();
96
+ }
97
+ _onMessage(t) {
98
+ var i, r, a, c;
99
+ const e = t.data;
100
+ if (e.type === "ready") {
101
+ const s = this.pending.get("__ready__");
102
+ s && (this.pending.delete("__ready__"), s.resolve(void 0), (r = (i = this._handlers).onReady) == null || r.call(i));
103
+ return;
104
+ }
105
+ if (e.type === "scan") {
106
+ const s = e.__id, n = s ? this.pending.get(s) : void 0;
107
+ n && (this.pending.delete(s), n.resolve({ hits: e.hits, hitCount: e.hitCount }));
108
+ return;
109
+ }
110
+ if (e.type === "error") {
111
+ const s = new h(e.message);
112
+ (c = (a = this._handlers).onError) == null || c.call(a, s);
113
+ for (const n of this.pending.values())
114
+ n.reject(s);
115
+ this.pending.clear();
116
+ }
117
+ }
118
+ }
119
+ class L {
120
+ constructor(t, e, i) {
121
+ d(this, "worker");
122
+ d(this, "pending", /* @__PURE__ */ new Map());
123
+ d(this, "_counter", 0);
124
+ d(this, "_config");
125
+ d(this, "_disposed", !1);
126
+ d(this, "_handlers");
127
+ this._config = t, this._handlers = i ?? {};
128
+ const r = e ?? new URL("./worker.js", import.meta.url);
129
+ this.worker = new Worker(r, { type: "module" }), this.worker.addEventListener("message", this._onMessage.bind(this));
130
+ }
131
+ /**
132
+ * Initialise the Wasm module inside the Web Worker.
133
+ * Must be called before {@link updateEnvironment} or {@link scan}.
134
+ */
135
+ init() {
136
+ return this._disposed ? Promise.reject(new p()) : new Promise((t, e) => {
137
+ this.pending.set("__ready__", {
138
+ resolve: t,
139
+ reject: e
140
+ }), this.worker.postMessage({ type: "init", config: this._config });
141
+ });
142
+ }
143
+ /**
144
+ * Load (or replace) the environment geometry and rebuild the BVH.
145
+ * Buffers are transferred to the worker to avoid copying.
146
+ */
147
+ updateEnvironment(t) {
148
+ return this._disposed ? Promise.reject(new p()) : new Promise((e, i) => {
149
+ const r = `env_${++this._counter}`;
150
+ this.pending.set(r, {
151
+ resolve: e,
152
+ reject: i
153
+ });
154
+ const a = new Float32Array(t.vertices), c = new Uint32Array(t.indices);
155
+ this.worker.postMessage(
156
+ { type: "updateEnvironment", vertices: a, indices: c, __id: r },
157
+ [a.buffer, c.buffer]
158
+ );
159
+ });
160
+ }
161
+ /**
162
+ * Run a full LiDAR scan from the given pose.
163
+ *
164
+ * @returns A `Promise` that resolves with a `Float32Array` of world-space
165
+ * hit coordinates `[x,y,z, x,y,z, …]`.
166
+ */
167
+ scan(t) {
168
+ return this._disposed ? Promise.reject(new p()) : new Promise((e, i) => {
169
+ const r = `scan_${++this._counter}`;
170
+ this.pending.set(r, {
171
+ resolve: e,
172
+ reject: i
173
+ }), this.worker.postMessage({ type: "scan", pose: t, __id: r });
174
+ });
175
+ }
176
+ /**
177
+ * Destroy the simulator: asks the worker to explicitly free the Wasm
178
+ * instance (releasing linear memory) and then terminates the worker thread.
179
+ *
180
+ * Any in-flight Promises are rejected with a {@link SimLidarDisposedError}.
181
+ */
182
+ destroy() {
183
+ if (this._disposed) return;
184
+ this._disposed = !0;
185
+ const t = new p();
186
+ for (const e of this.pending.values())
187
+ e.reject(t);
188
+ this.pending.clear(), this.worker.postMessage({ type: "destroy" });
189
+ }
190
+ _onMessage(t) {
191
+ var i, r, a, c;
192
+ const e = t.data;
193
+ if (e.type === "ready") {
194
+ const s = this.pending.get("__ready__");
195
+ s && (this.pending.delete("__ready__"), s.resolve(void 0), (r = (i = this._handlers).onReady) == null || r.call(i));
196
+ return;
197
+ }
198
+ if (e.type === "environmentUpdated") {
199
+ const s = e.__id;
200
+ if (s) {
201
+ const n = this.pending.get(s);
202
+ n && (this.pending.delete(s), n.resolve(void 0));
203
+ }
204
+ return;
205
+ }
206
+ if (e.type === "scan") {
207
+ const s = e.__id;
208
+ if (s) {
209
+ const n = this.pending.get(s);
210
+ n && (this.pending.delete(s), n.resolve(e.hits));
211
+ }
212
+ return;
213
+ }
214
+ if (e.type === "destroyed") {
215
+ this.worker.terminate();
216
+ return;
217
+ }
218
+ if (e.type === "error") {
219
+ const s = new h(e.message);
220
+ (c = (a = this._handlers).onError) == null || c.call(a, s);
221
+ for (const n of this.pending.values())
222
+ n.reject(s);
223
+ this.pending.clear();
224
+ }
225
+ }
226
+ }
227
+ export {
228
+ C as LidarClient,
229
+ w as OUSTER_OS1_32_CONFIG,
230
+ m as OUSTER_OS1_64_CONFIG,
231
+ L as SimLidar,
232
+ p as SimLidarDisposedError,
233
+ h as SimLidarError,
234
+ k as SimLidarNotInitializedError,
235
+ f as VLP16_CONFIG,
236
+ v as totalRays
237
+ };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * sim-lidar-rs/three – Optional Three.js adapter
3
+ *
4
+ * This module does **not** import from the `three` package at runtime, so it
5
+ * carries zero extra bundle weight for projects that do not use Three.js.
6
+ * The exported helpers use structural typing to accept real Three.js objects.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import * as THREE from 'three';
11
+ * import { extractGeometry, hitsToPoints } from 'sim-lidar-rs/three';
12
+ * import { SimLidar, VLP16_CONFIG } from 'sim-lidar-rs';
13
+ *
14
+ * // ── Build the LiDAR environment from your scene ──────────────────────────
15
+ * scene.updateMatrixWorld();
16
+ * const geometry = extractGeometry(scene);
17
+ *
18
+ * const lidar = new SimLidar(VLP16_CONFIG);
19
+ * await lidar.init();
20
+ * await lidar.updateEnvironment(geometry);
21
+ *
22
+ * // ── Visualise scan results in the same scene ──────────────────────────────
23
+ * const hits = await lidar.scan({ position: { x: 0, y: 1.5, z: 0 } });
24
+ * const points = hitsToPoints(hits, THREE);
25
+ * scene.add(points);
26
+ * ```
27
+ */
28
+ import type { Geometry } from "./types.js";
29
+ interface BufferAttribute {
30
+ /** Underlying flat typed array (e.g. Float32Array, Uint32Array). */
31
+ readonly array: ArrayLike<number>;
32
+ /** Number of elements (vertices / indices). */
33
+ readonly count: number;
34
+ /** Components per element (3 for position, 1 for scalar index). */
35
+ readonly itemSize: number;
36
+ }
37
+ interface BufferGeometry {
38
+ readonly attributes: Readonly<Record<string, BufferAttribute | undefined>>;
39
+ /** `null` for non-indexed geometries. */
40
+ readonly index: BufferAttribute | null;
41
+ }
42
+ interface Matrix4Like {
43
+ /** Column-major 16-element array, matching `THREE.Matrix4.elements`. */
44
+ readonly elements: ArrayLike<number>;
45
+ }
46
+ /** Structural type covering both `THREE.Scene` and `THREE.Mesh`. */
47
+ interface Object3D {
48
+ /** `true` for `THREE.Mesh` instances. */
49
+ readonly isMesh?: boolean;
50
+ /** Present on mesh objects. */
51
+ readonly geometry?: BufferGeometry;
52
+ /** Child objects in the scene graph. */
53
+ readonly children: readonly Object3D[];
54
+ /**
55
+ * World-space transformation matrix.
56
+ * Call `scene.updateMatrixWorld()` before passing the scene to ensure
57
+ * matrices are current.
58
+ */
59
+ readonly matrixWorld?: Matrix4Like;
60
+ }
61
+ /**
62
+ * Recursively traverse a `THREE.Scene` or `THREE.Mesh` and merge every mesh's
63
+ * vertex positions and triangle indices into a single flat {@link Geometry}
64
+ * ready for {@link SimLidar.updateEnvironment} / {@link LidarClient}.
65
+ *
66
+ * - **Matrix transforms**: if a mesh exposes a `matrixWorld` property its
67
+ * positions are converted to world space (column-major, Three.js convention).
68
+ * Call `scene.updateMatrixWorld()` before calling this function.
69
+ * - **Non-indexed geometries**: auto-generated sequential indices are added.
70
+ * - Multiple meshes are merged into a single vertex/index buffer pair.
71
+ *
72
+ * @param object - A `THREE.Scene`, `THREE.Mesh`, or any object that structurally
73
+ * matches the {@link Object3D} interface.
74
+ * @returns Flat {@link Geometry} with `vertices` and `indices` buffers.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * scene.updateMatrixWorld();
79
+ * const { vertices, indices } = extractGeometry(scene);
80
+ * await lidar.updateEnvironment({ vertices, indices });
81
+ * ```
82
+ */
83
+ export declare function extractGeometry(object: Object3D): Geometry;
84
+ /**
85
+ * Convert a flat `Float32Array` of `[x,y,z, x,y,z, …]` hit coordinates (as
86
+ * returned by {@link SimLidar.scan} or {@link LidarClient.scan}) into a
87
+ * `THREE.Points` object for immediate browser visualisation.
88
+ *
89
+ * The Three.js namespace is accepted as the second argument so this helper
90
+ * does **not** carry a hard dependency on the `three` npm package.
91
+ * TypeScript will infer the return type as `THREE.Points` when you pass the
92
+ * full `THREE` namespace.
93
+ *
94
+ * @param hits - Flat `Float32Array` of hit coordinates `[x,y,z, …]`.
95
+ * @param three - An object exposing `BufferGeometry`, `Float32BufferAttribute`,
96
+ * and `Points` constructors (pass `import * as THREE from 'three'`).
97
+ * @returns A `THREE.Points` instance ready to be added to a scene.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * import * as THREE from 'three';
102
+ * import { hitsToPoints } from 'sim-lidar-rs/three';
103
+ *
104
+ * const hits = await lidar.scan(pose);
105
+ * const points = hitsToPoints(hits, THREE);
106
+ * scene.add(points);
107
+ * ```
108
+ */
109
+ export declare function hitsToPoints<TPoints = unknown>(hits: Float32Array, three: {
110
+ BufferGeometry: new () => {
111
+ setAttribute(name: string, attribute: unknown): void;
112
+ };
113
+ Float32BufferAttribute: new (array: ArrayLike<number>, itemSize: number) => unknown;
114
+ Points: new (geometry: unknown, material?: unknown) => TPoints;
115
+ }): TPoints;
116
+ export {};
package/dist/three.js ADDED
@@ -0,0 +1,42 @@
1
+ function g(l) {
2
+ const n = [], s = [];
3
+ function d(o) {
4
+ var p;
5
+ if (o.isMesh && o.geometry) {
6
+ const r = o.geometry.attributes.position;
7
+ if (r) {
8
+ const A = n.length / 3, t = (p = o.matrixWorld) == null ? void 0 : p.elements, a = r.array;
9
+ for (let e = 0; e < r.count; e++) {
10
+ const c = e * r.itemSize;
11
+ let i = a[c], f = a[c + 1], u = a[c + 2];
12
+ if (t) {
13
+ const y = i, m = f, x = u;
14
+ i = t[0] * y + t[4] * m + t[8] * x + t[12], f = t[1] * y + t[5] * m + t[9] * x + t[13], u = t[2] * y + t[6] * m + t[10] * x + t[14];
15
+ }
16
+ n.push(i, f, u);
17
+ }
18
+ if (o.geometry.index) {
19
+ const e = o.geometry.index, c = e.array;
20
+ for (let i = 0; i < e.count; i++)
21
+ s.push(A + c[i]);
22
+ } else
23
+ for (let e = 0; e < r.count; e++)
24
+ s.push(A + e);
25
+ }
26
+ }
27
+ for (const r of o.children)
28
+ d(r);
29
+ }
30
+ return d(l), {
31
+ vertices: new Float32Array(n),
32
+ indices: new Uint32Array(s)
33
+ };
34
+ }
35
+ function h(l, n) {
36
+ const s = new n.BufferGeometry();
37
+ return s.setAttribute("position", new n.Float32BufferAttribute(l, 3)), new n.Points(s);
38
+ }
39
+ export {
40
+ g as extractGeometry,
41
+ h as hitsToPoints
42
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Sensor configuration mirroring real-world LiDARs (e.g. Velodyne VLP-16, Ouster).
3
+ */
4
+ export interface SensorConfig {
5
+ /** Number of rays per full horizontal sweep (360°). */
6
+ horizontalResolution: number;
7
+ /** Number of vertical laser rings (e.g. 16, 32, 64). */
8
+ verticalChannels: number;
9
+ /** Upper vertical FOV limit in degrees (e.g. +15 for VLP-16). */
10
+ verticalFovUpper: number;
11
+ /** Lower vertical FOV limit in degrees (e.g. -15 for VLP-16). */
12
+ verticalFovLower: number;
13
+ /** Minimum valid range in metres. */
14
+ minRange: number;
15
+ /** Maximum valid range in metres. */
16
+ maxRange: number;
17
+ /** Standard deviation of Gaussian noise added to range measurements. 0 = disabled. */
18
+ noiseStddev: number;
19
+ }
20
+ /**
21
+ * Sensor pose: position + orientation.
22
+ */
23
+ export interface Pose {
24
+ /** World-space sensor origin. */
25
+ position: {
26
+ x: number;
27
+ y: number;
28
+ z: number;
29
+ };
30
+ /**
31
+ * Sensor orientation as a unit quaternion.
32
+ * Defaults to identity `{ x:0, y:0, z:0, w:1 }` if omitted.
33
+ */
34
+ rotation?: {
35
+ x: number;
36
+ y: number;
37
+ z: number;
38
+ w: number;
39
+ };
40
+ }
41
+ /**
42
+ * Result of a single LiDAR scan.
43
+ */
44
+ export interface ScanResult {
45
+ /**
46
+ * Flat `Float32Array` of hit world-space coordinates: `[x,y,z, x,y,z, …]`.
47
+ * Length is `hitCount * 3`.
48
+ */
49
+ hits: Float32Array;
50
+ /** Number of valid hits returned. */
51
+ hitCount: number;
52
+ }
53
+ /** VLP-16 preset. */
54
+ export declare const VLP16_CONFIG: Readonly<SensorConfig>;
55
+ /** Ouster OS1-32 preset. */
56
+ export declare const OUSTER_OS1_32_CONFIG: Readonly<SensorConfig>;
57
+ /** Ouster OS1-64 preset. */
58
+ export declare const OUSTER_OS1_64_CONFIG: Readonly<SensorConfig>;
59
+ /**
60
+ * Returns the total number of rays fired per scan for a given config.
61
+ */
62
+ export declare function totalRays(config: SensorConfig): number;
63
+ /**
64
+ * Environment geometry consumed by {@link SimLidar.updateEnvironment}.
65
+ */
66
+ export interface Geometry {
67
+ /** Flat array of vertex positions `[x,y,z, …]`. */
68
+ vertices: Float32Array;
69
+ /** Flat array of triangle vertex indices. */
70
+ indices: Uint32Array;
71
+ }
72
+ /**
73
+ * Base error class for all sim-lidar-rs errors.
74
+ * Exported so consumers can use `err instanceof SimLidarError` to distinguish
75
+ * library errors from other runtime errors.
76
+ */
77
+ export declare class SimLidarError extends Error {
78
+ constructor(message: string);
79
+ }
80
+ /**
81
+ * Thrown when a scan or environment update is requested before the simulator
82
+ * has been initialised (i.e. before `init()` or `ready()` has resolved).
83
+ */
84
+ export declare class SimLidarNotInitializedError extends SimLidarError {
85
+ constructor();
86
+ }
87
+ /**
88
+ * Thrown when any method is called on a simulator instance that has already
89
+ * been disposed via `destroy()` or `dispose()`.
90
+ */
91
+ export declare class SimLidarDisposedError extends SimLidarError {
92
+ constructor();
93
+ }
94
+ /**
95
+ * Optional event callbacks that can be passed to {@link SimLidar} and
96
+ * {@link LidarClient} constructors for observability and debugging.
97
+ */
98
+ export interface SimLidarEventHandlers {
99
+ /**
100
+ * Called once the Wasm module has been loaded and the simulator is ready.
101
+ * Equivalent to awaiting `init()` / `ready()`.
102
+ */
103
+ onReady?: () => void;
104
+ /**
105
+ * Called whenever a worker-level error occurs (e.g. Wasm panic, unhandled
106
+ * exception inside the worker). Provides the error before it propagates to
107
+ * pending Promise rejections.
108
+ */
109
+ onError?: (err: SimLidarError) => void;
110
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * worker.ts – Web Worker script that hosts the Wasm module.
3
+ *
4
+ * Messages sent TO the worker:
5
+ * { type: 'init', config: SensorConfig, vertices?: Float32Array, indices?: Uint32Array }
6
+ * { type: 'updateEnvironment', vertices: Float32Array, indices: Uint32Array, __id: string }
7
+ * { type: 'scan', pose: Pose, __id: string }
8
+ * { type: 'setConfig', config: SensorConfig }
9
+ * { type: 'destroy' }
10
+ *
11
+ * Messages posted FROM the worker:
12
+ * { type: 'ready' }
13
+ * { type: 'environmentUpdated', __id: string }
14
+ * { type: 'scan', hits: Float32Array, hitCount: number, __id: string }
15
+ * { type: 'destroyed' }
16
+ * { type: 'error', message: string }
17
+ */
18
+ export {};
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "sim-lidar-rs",
3
+ "version": "0.1.0",
4
+ "description": "High-performance simulated LiDAR sensor library for WebAssembly",
5
+ "type": "module",
6
+ "main": "./dist/sim-lidar-rs.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/sim-lidar-rs.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./three": {
14
+ "import": "./dist/three.js",
15
+ "types": "./dist/three.d.ts"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build:wasm": "wasm-pack build --target web --out-dir ts/wasm",
20
+ "build:types": "tsc --emitDeclarationOnly",
21
+ "build": "npm run build:wasm && vite build && npm run build:types",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "lint": "tsc --noEmit",
25
+ "docs": "typedoc",
26
+ "prepublishOnly": "npm run build && npm run lint && npm test",
27
+ "check-publish": "bash scripts/check-publish.sh",
28
+ "publish:npm": "bash scripts/check-publish.sh --publish"
29
+ },
30
+ "sideEffects": false,
31
+ "peerDependencies": {
32
+ "three": ">=0.150.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "three": {
36
+ "optional": true
37
+ }
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.19.35",
41
+ "@vitest/coverage-v8": "^1.0.0",
42
+ "typedoc": "^0.28.0",
43
+ "typescript": "^5.4.0",
44
+ "vite": "^5.2.0",
45
+ "vite-plugin-wasm": "^3.3.0",
46
+ "vitest": "^1.0.0"
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "ts/wasm"
51
+ ],
52
+ "license": "MIT",
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ }
56
+ }