lecodes-gl-lite 2.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.
Files changed (46) hide show
  1. package/README.md +27 -0
  2. package/package.json +33 -0
  3. package/src/assets/decoders/README.md +13 -0
  4. package/src/assets/decoders/basis_transcoder.js +19 -0
  5. package/src/assets/decoders/basis_transcoder.wasm +0 -0
  6. package/src/assets/decoders/draco_decoder.wasm +0 -0
  7. package/src/assets/decoders/draco_wasm_wrapper.js +117 -0
  8. package/src/assets/neutral_ibl128.ktx +0 -0
  9. package/src/core/animBridge.ts +51 -0
  10. package/src/core/boneNames.ts +22 -0
  11. package/src/core/frame.ts +230 -0
  12. package/src/core/glbStructure.ts +100 -0
  13. package/src/core/headlessBridge.ts +92 -0
  14. package/src/core/mapView.ts +195 -0
  15. package/src/core/math3d.ts +275 -0
  16. package/src/core/particleBridge.ts +154 -0
  17. package/src/core/physicsBridge.ts +305 -0
  18. package/src/core/recording.ts +59 -0
  19. package/src/core/sceneCore.ts +833 -0
  20. package/src/core/serialize.ts +144 -0
  21. package/src/gen/unsupported.gen.ts +212 -0
  22. package/src/index.ts +5 -0
  23. package/src/vendor/creator-anim/dist/creatorAnim.d.ts +123 -0
  24. package/src/vendor/creator-anim/dist/creatorAnim.js +14 -0
  25. package/src/vendor/creator-anim/js/index.ts +656 -0
  26. package/src/vendor/creator-nav/dist/creatorNav.d.ts +56 -0
  27. package/src/vendor/creator-nav/dist/creatorNav.js +14 -0
  28. package/src/vendor/creator-nav/js/index.ts +400 -0
  29. package/src/vendor/creator-particles/dist/creatorParticles.d.ts +43 -0
  30. package/src/vendor/creator-particles/dist/creatorParticles.js +14 -0
  31. package/src/vendor/creator-particles/js/index.ts +137 -0
  32. package/src/vendor/creator-physics/dist/creatorPhysics.d.ts +107 -0
  33. package/src/vendor/creator-physics/dist/creatorPhysics.js +14 -0
  34. package/src/vendor/creator-physics/js/index.ts +515 -0
  35. package/src/vite-env.d.ts +1 -0
  36. package/src/webgl/batching.ts +453 -0
  37. package/src/webgl/createViewerLite.ts +1627 -0
  38. package/src/webgl/customMaterial.ts +228 -0
  39. package/src/webgl/decoders/basis.ts +123 -0
  40. package/src/webgl/decoders/draco.ts +110 -0
  41. package/src/webgl/decoders/index.ts +68 -0
  42. package/src/webgl/decoders/worker.ts +45 -0
  43. package/src/webgl/editor.ts +1297 -0
  44. package/src/webgl/glb.ts +593 -0
  45. package/src/webgl/ibl.ts +137 -0
  46. package/src/webgl/shaders.ts +779 -0
@@ -0,0 +1,400 @@
1
+ // creator-nav's TypeScript side over the wasm build (dist/creatorNav.js):
2
+ //
3
+ // • `createNavBridge(getBuffer)` — the `_creatorNav` object the WEB hosts (viewer, viewer-lite,
4
+ // lecodes-headless) hand to the app: the same method shapes as creator-pkg's
5
+ // creatorNavHandler.cpp, so the SDK never knows which host it runs on. The wasm loads lazily on
6
+ // `ready()`; a project without navigation never pays for it.
7
+ // • `NavBuilder` + `readNgeo` — the CLI's `lecodes navmesh bake`: a geometry dump (the desktop
8
+ // host's `physicsStaticGeometry`) → Recast → LNAV bytes.
9
+ //
10
+ // Parameter layouts mirror include/creator-nav/creator-nav.h (CNAV_BUILD_* / CNAV_AGENT_* /
11
+ // CNAV_READ_*) — keep the three in step.
12
+
13
+ import createCreatorNav, { type CreatorNavModule } from "../dist/creatorNav.js"
14
+
15
+ export const BUILD = {
16
+ CELL_SIZE: 0, CELL_HEIGHT: 1, AGENT_RADIUS: 2, AGENT_HEIGHT: 3, AGENT_MAX_CLIMB: 4, AGENT_MAX_SLOPE: 5,
17
+ TILE_SIZE: 6, REGION_MIN_SIZE: 7, REGION_MERGE_SIZE: 8, EDGE_MAX_LEN: 9, EDGE_MAX_ERROR: 10,
18
+ VERTS_PER_POLY: 11, DETAIL_SAMPLE_DIST: 12, DETAIL_MAX_ERROR: 13, FILTER_LOW_HANGING: 14,
19
+ FILTER_LEDGES: 15, FILTER_LOW_HEIGHT: 16, COUNT: 17,
20
+ } as const
21
+
22
+ export const AGENT = {
23
+ RADIUS: 0, HEIGHT: 1, MAX_SPEED: 2, MAX_ACCELERATION: 3, COLLISION_QUERY_RANGE: 4, PATH_OPTIMIZATION_RANGE: 5,
24
+ SEPARATION_WEIGHT: 6, AVOIDANCE: 7, INCLUDE_FLAGS: 8, EXCLUDE_FLAGS: 9, ANTICIPATE_TURNS: 10,
25
+ OPTIMIZE_VIS: 11, OPTIMIZE_TOPO: 12, COUNT: 13,
26
+ } as const
27
+
28
+ export const READ = {
29
+ POS_X: 0, POS_Y: 1, POS_Z: 2, VEL_X: 3, VEL_Y: 4, VEL_Z: 5, DVEL_X: 6, DVEL_Y: 7, DVEL_Z: 8,
30
+ STATE: 9, TARGET_STATE: 10, REMAINING: 11, PARTIAL: 12, STRIDE: 13,
31
+ } as const
32
+
33
+ export const AREA_UNWALKABLE = 255
34
+ export const FLAGS_ALL = 0xffff
35
+
36
+ export type BuildParams = Partial<{
37
+ cellSize: number, cellHeight: number, agentRadius: number, agentHeight: number, agentMaxClimb: number,
38
+ agentMaxSlope: number, tileSize: number, regionMinSize: number, regionMergeSize: number, edgeMaxLen: number,
39
+ edgeMaxError: number, vertsPerPoly: number, detailSampleDist: number, detailMaxError: number,
40
+ }>
41
+
42
+ const BUILD_KEYS: Record<keyof Required<BuildParams>, number> = {
43
+ cellSize: BUILD.CELL_SIZE, cellHeight: BUILD.CELL_HEIGHT, agentRadius: BUILD.AGENT_RADIUS, agentHeight: BUILD.AGENT_HEIGHT,
44
+ agentMaxClimb: BUILD.AGENT_MAX_CLIMB, agentMaxSlope: BUILD.AGENT_MAX_SLOPE, tileSize: BUILD.TILE_SIZE,
45
+ regionMinSize: BUILD.REGION_MIN_SIZE, regionMergeSize: BUILD.REGION_MERGE_SIZE, edgeMaxLen: BUILD.EDGE_MAX_LEN,
46
+ edgeMaxError: BUILD.EDGE_MAX_ERROR, vertsPerPoly: BUILD.VERTS_PER_POLY, detailSampleDist: BUILD.DETAIL_SAMPLE_DIST,
47
+ detailMaxError: BUILD.DETAIL_MAX_ERROR,
48
+ }
49
+
50
+ let modulePromise: Promise<CreatorNavModule> | null = null
51
+ /** The wasm module, loaded once per process (lazy). */
52
+ export const loadCreatorNav = (): Promise<CreatorNavModule> => (modulePromise ??= createCreatorNav())
53
+
54
+ // ---- heap helpers --------------------------------------------------------------------------------
55
+
56
+ const withF32 = <T>(mod: CreatorNavModule, values: ArrayLike<number>, fn: (ptr: number) => T): T => {
57
+ const ptr = mod._malloc(Math.max(4, values.length * 4))
58
+ mod.HEAPF32.set(values, ptr >> 2)
59
+ try { return fn(ptr) } finally { mod._free(ptr) }
60
+ }
61
+
62
+ const withString = <T>(mod: CreatorNavModule, s: string | null, fn: (ptr: number) => T): T => {
63
+ if (s === null) return fn(0)
64
+ const n = mod.lengthBytesUTF8(s) + 1
65
+ const ptr = mod._malloc(n)
66
+ mod.stringToUTF8(s, ptr, n)
67
+ try { return fn(ptr) } finally { mod._free(ptr) }
68
+ }
69
+
70
+ // ---- NGEO — the host's geometry dump -------------------------------------------------------------
71
+
72
+ export type NgeoData = {
73
+ /** 9 floats per triangle (three xyz), world space */
74
+ triangles: Float32Array
75
+ /** one area byte per triangle */
76
+ areas: Uint8Array
77
+ triangleCount: number
78
+ bodyCount: number
79
+ }
80
+
81
+ /** Parse a `physicsStaticGeometry` dump ('NGEO' u32 · version · triangleCount · bodyCount · records). */
82
+ export const readNgeo = (bytes: Uint8Array): NgeoData => {
83
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
84
+ if (bytes.byteLength < 16 || dv.getUint32(0, true) !== 0x4f45474e) throw new Error("not an NGEO geometry dump")
85
+ const version = dv.getUint32(4, true)
86
+ if (version !== 1) throw new Error(`NGEO version ${version}, expected 1`)
87
+ const triangleCount = dv.getUint32(8, true)
88
+ const bodyCount = dv.getUint32(12, true)
89
+ const triangles = new Float32Array(triangleCount * 9)
90
+ const areas = new Uint8Array(triangleCount)
91
+ let at = 16
92
+ for (let i = 0; i < triangleCount; i++) {
93
+ for (let k = 0; k < 9; k++) { triangles[i * 9 + k] = dv.getFloat32(at, true); at += 4 }
94
+ areas[i] = dv.getUint32(at, true) & 0xff
95
+ at += 4
96
+ }
97
+ return { triangles, areas, triangleCount, bodyCount }
98
+ }
99
+
100
+ // ---- NavBuilder — Recast over the wasm ------------------------------------------------------------
101
+
102
+ export type BuildResult = {
103
+ data: Uint8Array
104
+ tiles: number
105
+ polys: number
106
+ bounds: { min: [number, number, number], max: [number, number, number] }
107
+ /** seconds */
108
+ time: number
109
+ }
110
+
111
+ export class NavBuilder {
112
+ private handle: number
113
+
114
+ constructor(private readonly mod: CreatorNavModule, params: BuildParams = {}) {
115
+ const p = new Float32Array(BUILD.COUNT)
116
+ withF32(mod, p, (ptr) => { mod._cnavBuildDefaults(ptr); p.set(mod.HEAPF32.subarray(ptr >> 2, (ptr >> 2) + BUILD.COUNT)) })
117
+ for (const [key, value] of Object.entries(params)) {
118
+ if (value === undefined) continue
119
+ const idx = BUILD_KEYS[key as keyof BuildParams]
120
+ if (idx === undefined) throw new Error(`NavBuilder: unknown build parameter "${key}"`)
121
+ p[idx] = value
122
+ }
123
+ this.handle = withF32(mod, p, (ptr) => mod._cnavBuildCreate(ptr))
124
+ }
125
+
126
+ /** Indexed triangles; `area` for every triangle unless `areas` (one byte per triangle) is given. */
127
+ addTriangles(xyz: Float32Array, indices: Int32Array | Uint32Array, area = 0, areas?: Uint8Array): void {
128
+ const mod = this.mod
129
+ const vp = mod._malloc(xyz.byteLength)
130
+ mod.HEAPF32.set(xyz, vp >> 2)
131
+ const ip = mod._malloc(indices.byteLength)
132
+ mod.HEAP32.set(indices as unknown as Int32Array, ip >> 2)
133
+ let ap = 0
134
+ if (areas) { ap = mod._malloc(areas.byteLength); mod.HEAPU8.set(areas, ap) }
135
+ const ok = mod._cnavBuildAddTriangles(this.handle, vp, xyz.length / 3, ip, indices.length / 3, area, ap)
136
+ mod._free(vp); mod._free(ip); if (ap) mod._free(ap)
137
+ if (!ok) throw new Error(`NavBuilder.addTriangles: ${this.lastError()}`)
138
+ }
139
+
140
+ /** An unindexed soup (9 floats per triangle) — the NGEO dump's shape. */
141
+ addSoup(triangles: Float32Array, areas?: Uint8Array): void {
142
+ const n = triangles.length / 3
143
+ const indices = new Int32Array(n)
144
+ for (let i = 0; i < n; i++) indices[i] = i
145
+ this.addTriangles(triangles, indices, 0, areas)
146
+ }
147
+
148
+ addVolume(hullXZ: ArrayLike<number>, minY: number, maxY: number, area: number): void {
149
+ const ok = withF32(this.mod, hullXZ, (ptr) => this.mod._cnavBuildAddVolume(this.handle, ptr, hullXZ.length / 2, minY, maxY, area))
150
+ if (!ok) throw new Error(`NavBuilder.addVolume: ${this.lastError()}`)
151
+ }
152
+
153
+ addLink(a: ArrayLike<number>, b: ArrayLike<number>, radius: number, bidirectional = true, area = 0, userId = 0): void {
154
+ const ok = withF32(this.mod, [ a[0]!, a[1]!, a[2]!, b[0]!, b[1]!, b[2]! ], (ptr) =>
155
+ this.mod._cnavBuildAddLink(this.handle, ptr, ptr + 12, radius, bidirectional ? 1 : 0, area, userId))
156
+ if (!ok) throw new Error(`NavBuilder.addLink: ${this.lastError()}`)
157
+ }
158
+
159
+ run(meta: object | null = null, onProgress?: (tile: number, tileCount: number, message: string | null) => void): BuildResult {
160
+ const mod = this.mod
161
+ const t0 = performance.now()
162
+ let cb = 0
163
+ if (onProgress) {
164
+ cb = mod.addFunction((tile: number, count: number, msg: number) => {
165
+ onProgress(tile, count, msg ? mod.UTF8ToString(msg) : null)
166
+ }, "viiii")
167
+ }
168
+ const ok = withString(mod, meta ? JSON.stringify(meta) : null, (mp) => mod._cnavBuildRun(this.handle, mp, cb, 0))
169
+ if (cb) mod.removeFunction(cb)
170
+ if (!ok) throw new Error(`navmesh build failed: ${this.lastError()}`)
171
+ const sizePtr = mod._malloc(4)
172
+ const dataPtr = mod._cnavBuildData(this.handle, sizePtr)
173
+ const size = mod.HEAPU32[sizePtr >> 2]!
174
+ mod._free(sizePtr)
175
+ const data = new Uint8Array(mod.HEAPU8.subarray(dataPtr, dataPtr + size)) // a copy
176
+ const bp = mod._malloc(24)
177
+ mod._cnavBuildBounds(this.handle, bp, bp + 12)
178
+ const b = mod.HEAPF32.subarray(bp >> 2, (bp >> 2) + 6)
179
+ const bounds = { min: [ b[0]!, b[1]!, b[2]! ] as [number, number, number], max: [ b[3]!, b[4]!, b[5]! ] as [number, number, number] }
180
+ mod._free(bp)
181
+ return { data, tiles: mod._cnavBuildTileCount(this.handle), polys: mod._cnavBuildPolyCount(this.handle), bounds, time: (performance.now() - t0) / 1000 }
182
+ }
183
+
184
+ lastError(): string { return this.mod.UTF8ToString(this.mod._cnavLastError()) }
185
+
186
+ destroy(): void {
187
+ if (this.handle) { this.mod._cnavBuildDestroy(this.handle); this.handle = 0 }
188
+ }
189
+ }
190
+
191
+ /** The polygon mesh of an LNAV file as a triangle soup (xyz per vertex) — the CLI's `--obj` debug dump. */
192
+ export const navMeshTriangles = (mod: CreatorNavModule, data: Uint8Array): Float32Array => {
193
+ const ptr = mod._malloc(data.byteLength)
194
+ mod.HEAPU8.set(data, ptr)
195
+ const mesh = mod._cnavMeshLoad(ptr, data.byteLength)
196
+ mod._free(ptr)
197
+ if (!mesh) throw new Error(`navmesh load failed: ${mod.UTF8ToString(mod._cnavLastError())}`)
198
+ const n = mod._cnavDebugTriangles(mesh, 0, 0)
199
+ const out = new Float32Array(n * 3)
200
+ if (n > 0) {
201
+ const op = mod._malloc(n * 12)
202
+ mod._cnavDebugTriangles(mesh, op, n)
203
+ out.set(mod.HEAPF32.subarray(op >> 2, (op >> 2) + n * 3))
204
+ mod._free(op)
205
+ }
206
+ mod._cnavMeshDestroy(mesh)
207
+ return out
208
+ }
209
+
210
+ // ---- the `_creatorNav` bridge for the web hosts ---------------------------------------------------
211
+
212
+ export type NavBridge = {
213
+ /** Web hosts only: load the wasm. Native hosts have no such method (the SDK awaits it when present). */
214
+ ready(): Promise<void>
215
+ lastError(): string
216
+ load(systemId: number): number
217
+ unload(meshId: number): void
218
+ header(meshId: number): string
219
+ polyCount(meshId: number): number
220
+ setAreaCost(meshId: number, area: number, cost: number): void
221
+ findPath(meshId: number, fx: number, fy: number, fz: number, tx: number, ty: number, tz: number, ex: number, ey: number, ez: number, include: number, exclude: number, out: Float32Array): number
222
+ nearest(meshId: number, x: number, y: number, z: number, ex: number, ey: number, ez: number, include: number, exclude: number, out: Float32Array): boolean
223
+ raycast(meshId: number, fx: number, fy: number, fz: number, tx: number, ty: number, tz: number, include: number, exclude: number, out: Float32Array): boolean
224
+ randomPoint(meshId: number, cx: number, cy: number, cz: number, radius: number, seed: number, include: number, exclude: number, out: Float32Array): boolean
225
+ debugTriangles(meshId: number): Float32Array
226
+ crowdCreate(meshId: number, maxAgents: number, maxRadius: number): number
227
+ crowdDestroy(crowdId: number): void
228
+ crowdAdd(crowdId: number, x: number, y: number, z: number, params: Float32Array): number
229
+ crowdRemove(crowdId: number, agent: number): void
230
+ crowdSetParams(crowdId: number, agent: number, params: Float32Array): void
231
+ crowdSetTarget(crowdId: number, agent: number, x: number, y: number, z: number): boolean
232
+ crowdSetVelocity(crowdId: number, agent: number, vx: number, vy: number, vz: number): void
233
+ crowdResetTarget(crowdId: number, agent: number): void
234
+ crowdWarp(crowdId: number, agent: number, x: number, y: number, z: number): boolean
235
+ crowdUpdate(crowdId: number, dt: number): void
236
+ crowdRead(crowdId: number, out: Float32Array): number
237
+ }
238
+
239
+ /** The web hosts' `_creatorNav`. `getBuffer` resolves a fetch system id to its bytes (the host's
240
+ * fetch store — viewer-lite's `getBuffer`, the renderer's `services.getBuffer`). */
241
+ export const createNavBridge = (getBuffer: (systemId: number) => ArrayBuffer | Uint8Array | null | undefined): NavBridge => {
242
+ let mod: CreatorNavModule | null = null
243
+ const meshes = new Map<number, number>() // meshId → cnavMesh*
244
+ const crowds = new Map<number, { ptr: number, max: number, buf: number }>()
245
+ let nextId = 1
246
+ // scratch heap for small in/out arrays (48 floats: enough for every fixed-size argument set)
247
+ let scratch = 0
248
+ const s = (): number => scratch || (scratch = mod!._malloc(48 * 4))
249
+ const sf = (offset: number, values: ArrayLike<number>): number => {
250
+ const ptr = s() + offset * 4
251
+ mod!.HEAPF32.set(values, ptr >> 2)
252
+ return ptr
253
+ }
254
+ const readF = (ptr: number, n: number, out: Float32Array): void => { out.set(mod!.HEAPF32.subarray(ptr >> 2, (ptr >> 2) + n)) }
255
+ const need = (): CreatorNavModule => {
256
+ if (!mod) throw new Error("_creatorNav: call ready() before loading a navmesh")
257
+ return mod
258
+ }
259
+
260
+ return {
261
+ async ready() { mod = await loadCreatorNav() },
262
+ lastError() { return mod ? mod.UTF8ToString(mod._cnavLastError()) : "wasm not loaded" },
263
+
264
+ load(systemId) {
265
+ const m = need()
266
+ const buf = getBuffer(systemId)
267
+ if (!buf) return 0
268
+ const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
269
+ const ptr = m._malloc(bytes.byteLength)
270
+ m.HEAPU8.set(bytes, ptr)
271
+ const mesh = m._cnavMeshLoad(ptr, bytes.byteLength)
272
+ m._free(ptr)
273
+ if (!mesh) return 0
274
+ const id = nextId++
275
+ meshes.set(id, mesh)
276
+ return id
277
+ },
278
+ unload(meshId) {
279
+ const mesh = meshes.get(meshId)
280
+ if (!mesh) return
281
+ for (const [ cid, c ] of crowds) { need()._cnavCrowdDestroy(c.ptr); need()._free(c.buf); crowds.delete(cid) }
282
+ need()._cnavMeshDestroy(mesh)
283
+ meshes.delete(meshId)
284
+ },
285
+ header(meshId) {
286
+ const mesh = meshes.get(meshId)
287
+ return mesh ? need().UTF8ToString(need()._cnavMeshHeader(mesh)) : ""
288
+ },
289
+ polyCount(meshId) {
290
+ const mesh = meshes.get(meshId)
291
+ return mesh ? need()._cnavMeshPolyCount(mesh) : 0
292
+ },
293
+ setAreaCost(meshId, area, cost) {
294
+ const mesh = meshes.get(meshId)
295
+ if (mesh) need()._cnavMeshSetAreaCost(mesh, area, cost)
296
+ },
297
+
298
+ findPath(meshId, fx, fy, fz, tx, ty, tz, ex, ey, ez, include, exclude, out) {
299
+ const mesh = meshes.get(meshId)
300
+ if (!mesh) return 0
301
+ const m = need()
302
+ const n = Math.floor(out.length / 3)
303
+ const from = sf(0, [ fx, fy, fz ]), to = sf(3, [ tx, ty, tz ]), ext = ex > 0 ? sf(6, [ ex, ey, ez ]) : 0
304
+ const partial = s() + 9 * 4
305
+ m.HEAP32[partial >> 2] = 0
306
+ const outPtr = m._malloc(Math.max(1, n) * 12)
307
+ const count = m._cnavFindPath(mesh, from, to, ext, include, exclude, outPtr, n, partial)
308
+ if (count > 0) readF(outPtr, count * 3, out)
309
+ const isPartial = m.HEAPU8[partial] !== 0
310
+ m._free(outPtr)
311
+ return isPartial ? -count : count
312
+ },
313
+ nearest(meshId, x, y, z, ex, ey, ez, include, exclude, out) {
314
+ const mesh = meshes.get(meshId)
315
+ if (!mesh) return false
316
+ const m = need()
317
+ const p = sf(0, [ x, y, z ]), ext = ex > 0 ? sf(3, [ ex, ey, ez ]) : 0, o = s() + 6 * 4
318
+ const ok = m._cnavNearest(mesh, p, ext, include, exclude, o) !== 0
319
+ if (ok) readF(o, 3, out)
320
+ return ok
321
+ },
322
+ raycast(meshId, fx, fy, fz, tx, ty, tz, include, exclude, out) {
323
+ const mesh = meshes.get(meshId)
324
+ if (!mesh) return true
325
+ const m = need()
326
+ const from = sf(0, [ fx, fy, fz ]), to = sf(3, [ tx, ty, tz ]), hit = s() + 6 * 4, nrm = s() + 9 * 4
327
+ const blocked = m._cnavRaycast(mesh, from, to, include, exclude, hit, nrm) !== 0
328
+ readF(hit, 6, out)
329
+ return blocked
330
+ },
331
+ randomPoint(meshId, cx, cy, cz, radius, seed, include, exclude, out) {
332
+ const mesh = meshes.get(meshId)
333
+ if (!mesh) return false
334
+ const m = need()
335
+ const c = radius > 0 ? sf(0, [ cx, cy, cz ]) : 0, o = s() + 3 * 4
336
+ const ok = m._cnavRandomPoint(mesh, c, radius, seed >>> 0, include, exclude, o) !== 0
337
+ if (ok) readF(o, 3, out)
338
+ return ok
339
+ },
340
+ debugTriangles(meshId) {
341
+ const mesh = meshes.get(meshId)
342
+ if (!mesh) return new Float32Array(0)
343
+ const m = need()
344
+ const n = m._cnavDebugTriangles(mesh, 0, 0)
345
+ const ptr = m._malloc(Math.max(1, n) * 12)
346
+ m._cnavDebugTriangles(mesh, ptr, n)
347
+ const out = new Float32Array(n * 3)
348
+ readF(ptr, n * 3, out)
349
+ m._free(ptr)
350
+ return out
351
+ },
352
+
353
+ crowdCreate(meshId, maxAgents, maxRadius) {
354
+ const mesh = meshes.get(meshId)
355
+ if (!mesh) return 0
356
+ const m = need()
357
+ const ptr = m._cnavCrowdCreate(mesh, maxAgents, maxRadius)
358
+ if (!ptr) return 0
359
+ const id = nextId++
360
+ crowds.set(id, { ptr, max: maxAgents, buf: m._malloc(maxAgents * READ.STRIDE * 4) })
361
+ return id
362
+ },
363
+ crowdDestroy(crowdId) {
364
+ const c = crowds.get(crowdId)
365
+ if (!c) return
366
+ need()._cnavCrowdDestroy(c.ptr)
367
+ need()._free(c.buf)
368
+ crowds.delete(crowdId)
369
+ },
370
+ crowdAdd(crowdId, x, y, z, params) {
371
+ const c = crowds.get(crowdId)
372
+ if (!c) return -1
373
+ const pos = sf(0, [ x, y, z ]), p = sf(3, params)
374
+ return need()._cnavCrowdAdd(c.ptr, pos, params.length >= AGENT.COUNT ? p : 0)
375
+ },
376
+ crowdRemove(crowdId, agent) { const c = crowds.get(crowdId); if (c) need()._cnavCrowdRemove(c.ptr, agent) },
377
+ crowdSetParams(crowdId, agent, params) {
378
+ const c = crowds.get(crowdId)
379
+ if (c && params.length >= AGENT.COUNT) need()._cnavCrowdSetParams(c.ptr, agent, sf(0, params))
380
+ },
381
+ crowdSetTarget(crowdId, agent, x, y, z) {
382
+ const c = crowds.get(crowdId)
383
+ return c ? need()._cnavCrowdSetTarget(c.ptr, agent, sf(0, [ x, y, z ])) !== 0 : false
384
+ },
385
+ crowdSetVelocity(crowdId, agent, vx, vy, vz) { const c = crowds.get(crowdId); if (c) need()._cnavCrowdSetVelocity(c.ptr, agent, sf(0, [ vx, vy, vz ])) },
386
+ crowdResetTarget(crowdId, agent) { const c = crowds.get(crowdId); if (c) need()._cnavCrowdResetTarget(c.ptr, agent) },
387
+ crowdWarp(crowdId, agent, x, y, z) {
388
+ const c = crowds.get(crowdId)
389
+ return c ? need()._cnavCrowdWarp(c.ptr, agent, sf(0, [ x, y, z ])) !== 0 : false
390
+ },
391
+ crowdUpdate(crowdId, dt) { const c = crowds.get(crowdId); if (c) need()._cnavCrowdUpdate(c.ptr, dt) },
392
+ crowdRead(crowdId, out) {
393
+ const c = crowds.get(crowdId)
394
+ if (!c || out.length < c.max * READ.STRIDE) return 0
395
+ const n = need()._cnavCrowdRead(c.ptr, c.buf)
396
+ readF(c.buf, c.max * READ.STRIDE, out)
397
+ return n
398
+ },
399
+ }
400
+ }
@@ -0,0 +1,43 @@
1
+ // Type surface of the single-file Emscripten module build-wasm.sh emits next to this file.
2
+ // The exported C functions take heap pointers; use js/index.ts rather than this directly.
3
+ // Keep in step with the CPART_EXPORTS list in CMakeLists.txt and include/creator-particles/creator-particles.h.
4
+
5
+ export interface CreatorParticlesModule {
6
+ HEAPU8: Uint8Array
7
+ HEAP32: Int32Array
8
+ HEAPU32: Uint32Array
9
+ HEAPF32: Float32Array
10
+ _malloc(bytes: number): number
11
+ _free(ptr: number): void
12
+
13
+ _cpartCreate(key: number, maxParticles: number): number
14
+ _cpartDestroy(key: number): void
15
+ _cpartExists(key: number): number
16
+ _cpartReset(): void
17
+ _cpartSetConfig(key: number, data: number, length: number): void
18
+ _cpartSpawn(key: number, count: number): void
19
+ _cpartSetVisible(key: number, visible: number): void
20
+ _cpartMode(key: number): number
21
+ _cpartOrder(key: number): number
22
+ _cpartCapacity(key: number): number
23
+ _cpartGeometryVersion(key: number): number
24
+ _cpartMeshVertexCount(key: number): number
25
+ _cpartMeshIndexCount(key: number): number
26
+ _cpartIndexPatternCount(key: number): number
27
+ _cpartWriteIndexPattern(key: number, out: number): void
28
+ _cpartUpdate(key: number, dt: number, world16: number, cameraPos3: number): void
29
+ _cpartAlive(key: number): number
30
+ _cpartVertexCount(key: number): number
31
+ _cpartVertexData(key: number): number
32
+ _cpartVertexStride(key: number): number
33
+ _cpartIndexCount(key: number): number
34
+ _cpartNoiseCreate(): number
35
+ _cpartNoiseSetFrequency(id: number, frequency: number): void
36
+ _cpartNoiseSetOctaves(id: number, octaves: number): void
37
+ _cpartNoiseSetLacunarity(id: number, lacunarity: number): void
38
+ _cpartNoiseSetGain(id: number, gain: number): void
39
+ _cpartNoise2D(id: number, x: number, y: number): number
40
+ _cpartNoise3D(id: number, x: number, y: number, z: number): number
41
+ }
42
+
43
+ export default function createCreatorParticles(): Promise<CreatorParticlesModule>