@series-inc/rundot-3d-engine 0.6.18 → 0.6.20

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/SKILL.md CHANGED
@@ -335,10 +335,214 @@ const clone = await stowkit.cloneMesh("name", castShadow, receiveShadow)
335
335
  await stowkit.registerMeshForInstancing("coin_batch", "coin_mesh", true, true, 500)
336
336
  ```
337
337
 
338
+ ### InputManager — Keyboard, Pointer, Touch & Custom Actions
339
+
340
+ The `InputManager` is a singleton that tracks keyboard, pointer, and touch state. Use the `Input` convenience object for quick access.
341
+
342
+ **Built-in action polling (keyboard):**
343
+
344
+ ```typescript
345
+ import { Input, InputAction } from "@series-inc/rundot-3d-engine/systems"
346
+
347
+ // Check built-in movement actions
348
+ if (Input.isPressed(InputAction.MOVE_FORWARD)) { /* W key */ }
349
+ if (Input.isKeyPressed("Space")) { /* raw key check */ }
350
+ ```
351
+
352
+ **Pointer & touch state:**
353
+
354
+ ```typescript
355
+ Input.isPointerDown() // true while mouse/pen is held
356
+ Input.getPointerPosition() // { x, y } in client coords
357
+ ```
358
+
359
+ **Custom action system — register named actions with multiple trigger types:**
360
+
361
+ ```typescript
362
+ import { Input } from "@series-inc/rundot-3d-engine/systems"
363
+ import type { ActionTrigger } from "@series-inc/rundot-3d-engine/systems"
364
+
365
+ // Register an action with multiple triggers
366
+ Input.registerAction("jump", [
367
+ { type: "key", code: "Space" },
368
+ { type: "pointer" },
369
+ { type: "touch" },
370
+ ])
371
+
372
+ // Subscribe to action (fires on press) — returns unsubscribe function
373
+ const unsub = Input.onAction("jump", () => {
374
+ player.flap()
375
+ })
376
+
377
+ // Polling API
378
+ if (Input.isCustomActionActive("jump")) { /* any trigger held */ }
379
+
380
+ // Cleanup
381
+ unsub()
382
+ ```
383
+
384
+ **ActionTrigger types:**
385
+ - `{ type: "key", code: string }` — keyboard key (uses `event.code`, e.g. `"Space"`, `"KeyW"`)
386
+ - `{ type: "pointer" }` — mouse or pen pointer down (excludes touch — no double-firing)
387
+ - `{ type: "touch" }` — touch screen tap
388
+
389
+ **Notes:**
390
+ - On touch devices, pointer events from touch input are automatically skipped to prevent double-firing when an action has both `pointer` and `touch` triggers.
391
+ - `Input.setEnabled(false)` disables all input processing and clears all state.
392
+ - Custom actions registered with `registerAction` also get `preventDefault` on their key triggers.
393
+
394
+ ### ShaderRegistry & ShaderComponent — Shader Library
395
+
396
+ A registry of named shader presets with a component that applies them to meshes. Shared `time` and `deltaTime` uniforms are updated automatically once per frame.
397
+
398
+ **Built-in presets:**
399
+ - `"fresnel"` — Fresnel rim lighting, wrap diffuse, specular, under-lighting, directional light. Good for stylized characters and objects.
400
+ - `"wind"` — Simplex noise-based vertex displacement for vegetation animation. Includes depth shader pair for correct shadow casting with alpha clip.
401
+
402
+ **Usage — apply a shader preset to a GameObject:**
403
+
404
+ ```typescript
405
+ import { ShaderComponent } from "@series-inc/rundot-3d-engine/systems"
406
+
407
+ // Apply fresnel shader with default settings
408
+ gameObject.addComponent(new ShaderComponent("fresnel"))
409
+
410
+ // Apply with uniform overrides
411
+ gameObject.addComponent(new ShaderComponent("fresnel", {
412
+ fresnelIntensity: { value: 0.3 },
413
+ underLightIntensity: { value: 0.2 },
414
+ }))
415
+
416
+ // Apply wind shader (vegetation)
417
+ seaweedObject.addComponent(new ShaderComponent("wind"))
418
+ ```
419
+
420
+ **ShaderComponent behavior:**
421
+ - Waits for all `MeshRenderer` components in children to load before applying
422
+ - Traverses all child meshes and replaces materials with ShaderMaterial from the preset
423
+ - Automatically includes fog uniforms and shared time uniforms
424
+ - Sets `preventAutoInstancing = true` (custom materials can't be auto-batched)
425
+ - On cleanup, restores original materials and disposes created shader materials
426
+ - For presets with depth shaders (e.g. `"wind"`), creates `customDepthMaterial` and `customDistanceMaterial` for correct shadow casting
427
+
428
+ **Registering custom presets:**
429
+
430
+ ```typescript
431
+ import { ShaderRegistry } from "@series-inc/rundot-3d-engine/systems"
432
+ import type { ShaderPreset } from "@series-inc/rundot-3d-engine/systems"
433
+
434
+ ShaderRegistry.register({
435
+ name: "myShader",
436
+ vertexShader: `...`,
437
+ fragmentShader: `...`,
438
+ depthVertexShader: `...`, // optional — for shadow casting
439
+ depthFragmentShader: `...`, // optional
440
+ side: THREE.FrontSide, // optional (default: FrontSide)
441
+ transparent: false, // optional (default: false)
442
+ fog: true, // optional (default: true)
443
+ toneMapped: false, // optional (default: false)
444
+ defaultUniforms: {
445
+ myColor: { value: new THREE.Color(1, 0, 0) },
446
+ myFloat: { value: 1.0 },
447
+ },
448
+ })
449
+
450
+ // Then use it
451
+ gameObject.addComponent(new ShaderComponent("myShader"))
452
+ ```
453
+
454
+ **Shared uniforms (available in all shader presets):**
455
+ - `time` (float) — `performance.now() * 0.001` (seconds since page load)
456
+ - `deltaTime` (float) — frame delta time
457
+ - `baseMap` (sampler2D) — automatically set from the mesh's existing texture map
458
+
459
+ **Fresnel preset uniforms:** `sunDir`, `sunColor`, `sunIntensity`, `fresnelColor`, `fresnelPower`, `fresnelIntensity`, `ambientColor`, `ambientIntensity`, `specColor`, `specShininess`, `specIntensity`, `underLightColor`, `underLightIntensity`, `underLightPower`, `rimLightDir`, `rimLightColor`, `rimLightIntensity`
460
+
461
+ **Wind preset uniforms:** `windStrength`, `windSpeed`, `rootWorldOrigin`, `alphaClip`, `tintColor`, `lightDir`, `ambientColor`, `ambientStrength`
462
+
463
+ ### ObjectPool, GameObjectPool & ScrollingPool — Object Pooling
464
+
465
+ Generic and specialized pools for reusing objects instead of creating/destroying them each frame.
466
+
467
+ **ObjectPool — generic pool:**
468
+
469
+ ```typescript
470
+ import { ObjectPool } from "@series-inc/rundot-3d-engine/systems"
471
+
472
+ const pool = new ObjectPool({
473
+ create: () => new Bullet(),
474
+ onAcquire: (b) => b.activate(),
475
+ onRelease: (b) => b.deactivate(),
476
+ onDestroy: (b) => b.dispose(),
477
+ })
478
+
479
+ pool.warm(20) // pre-allocate 20 objects
480
+ const bullet = pool.acquire() // get from pool (or create new)
481
+ pool.release(bullet) // return to pool
482
+ pool.releaseAll() // return all active objects
483
+ pool.dispose() // destroy everything
484
+
485
+ pool.activeCount // currently in use
486
+ pool.availableCount // ready to acquire
487
+ pool.totalCount // active + available
488
+ ```
489
+
490
+ **GameObjectPool — pool for GameObjects with visibility management:**
491
+
492
+ ```typescript
493
+ import { GameObjectPool } from "@series-inc/rundot-3d-engine/systems"
494
+
495
+ const pipePool = new GameObjectPool(() => {
496
+ const go = new GameObject("Pipe")
497
+ go.addComponent(new MeshRenderer("pipe_mesh"))
498
+ return go
499
+ })
500
+
501
+ pipePool.warm(10)
502
+ const pipe = pipePool.acquire() // visible = true
503
+ pipe.position.set(0, 0, 50)
504
+
505
+ pipePool.release(pipe) // visible = false, moved offscreen (y = -9999)
506
+ pipePool.releaseAll()
507
+ pipePool.dispose() // calls gameObject.dispose() on all
508
+ ```
509
+
510
+ **ScrollingPool — infinite tile recycler for endless/runner games:**
511
+
512
+ ```typescript
513
+ import { ScrollingPool, GameObjectPool } from "@series-inc/rundot-3d-engine/systems"
514
+
515
+ const tilePool = new GameObjectPool(() => {
516
+ const tile = Prefabs.instantiate("Background").gameObject
517
+ return tile
518
+ })
519
+
520
+ const scroller = new ScrollingPool({
521
+ pool: tilePool,
522
+ tileSize: 250,
523
+ bufferCount: 3,
524
+ axis: "z", // "x" | "y" | "z" (default: "z")
525
+ onSpawn: (tile, index) => {
526
+ // customize each spawned tile
527
+ tile.position.x = 25
528
+ },
529
+ })
530
+
531
+ scroller.initialize() // spawn initial buffer
532
+
533
+ // In update loop:
534
+ scroller.update(playerZ) // spawns ahead, recycles behind
535
+
536
+ // On restart:
537
+ scroller.reset()
538
+ scroller.initialize()
539
+
540
+ scroller.dispose()
541
+ ```
542
+
338
543
  ### Other Systems
339
544
 
340
545
  - **AudioSystem** — 2D/3D positional audio with AudioListener management
341
- - **InputManager** — keyboard, mouse, touch, and gamepad input
342
546
  - **LightingSystem** — directional, point, and spot lights with shadow management
343
547
  - **NavigationSystem** — A* pathfinding on navigation meshes
344
548
  - **ParticleSystem** — GPU particle effects