@jgengine/shell 0.7.0 → 0.9.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 (168) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +37 -1
  3. package/dist/GameHost.d.ts +12 -0
  4. package/dist/GameHost.js +33 -0
  5. package/dist/GamePlayer.d.ts +11 -0
  6. package/dist/GamePlayer.js +24 -0
  7. package/dist/GamePlayerShell.d.ts +36 -1
  8. package/dist/GamePlayerShell.js +751 -80
  9. package/dist/GameUiPreview.js +2 -1
  10. package/dist/audio/AudioComponents.js +25 -8
  11. package/dist/audio/audioEngine.d.ts +1 -0
  12. package/dist/audio/audioEngine.js +4 -0
  13. package/dist/behaviour.d.ts +12 -0
  14. package/dist/behaviour.js +27 -0
  15. package/dist/behaviourAttach.d.ts +9 -0
  16. package/dist/behaviourAttach.js +32 -0
  17. package/dist/behaviourDriver.d.ts +7 -0
  18. package/dist/behaviourDriver.js +21 -0
  19. package/dist/camera/GameCameraRig.d.ts +8 -3
  20. package/dist/camera/GameCameraRig.js +28 -14
  21. package/dist/camera/GameFirstPersonCamera.js +16 -0
  22. package/dist/camera/GameInspectionCamera.d.ts +6 -0
  23. package/dist/camera/GameInspectionCamera.js +24 -0
  24. package/dist/camera/GameOrbitCamera.js +10 -1
  25. package/dist/camera/PlayerFov.d.ts +18 -0
  26. package/dist/camera/PlayerFov.js +48 -0
  27. package/dist/camera/cameraBlendMath.d.ts +13 -0
  28. package/dist/camera/cameraBlendMath.js +36 -0
  29. package/dist/camera/cameraRigs.d.ts +8 -1
  30. package/dist/camera/cameraRigs.js +78 -38
  31. package/dist/camera/fovPreference.d.ts +25 -0
  32. package/dist/camera/fovPreference.js +73 -0
  33. package/dist/camera/index.d.ts +7 -2
  34. package/dist/camera/index.js +7 -2
  35. package/dist/camera/inspectionCameraMath.d.ts +25 -0
  36. package/dist/camera/inspectionCameraMath.js +44 -0
  37. package/dist/camera/rigMath.d.ts +50 -1
  38. package/dist/camera/rigMath.js +91 -1
  39. package/dist/camera/rigResolve.d.ts +15 -0
  40. package/dist/camera/rigResolve.js +52 -0
  41. package/dist/camera/shakeChannel.d.ts +3 -17
  42. package/dist/camera/shakeChannel.js +2 -21
  43. package/dist/camera/shakeChannelMath.d.ts +8 -0
  44. package/dist/camera/shakeChannelMath.js +21 -0
  45. package/dist/cartridge.d.ts +145 -0
  46. package/dist/cartridge.js +245 -0
  47. package/dist/defineGame.d.ts +16 -0
  48. package/dist/defineGame.js +52 -0
  49. package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
  50. package/dist/devtools/CollisionDebugWorld.js +180 -0
  51. package/dist/devtools/DevtoolsOverlay.d.ts +78 -0
  52. package/dist/devtools/DevtoolsOverlay.js +676 -0
  53. package/dist/devtools/collisionDebug.d.ts +57 -0
  54. package/dist/devtools/collisionDebug.js +127 -0
  55. package/dist/devtools/collisionDebugMath.d.ts +102 -0
  56. package/dist/devtools/collisionDebugMath.js +128 -0
  57. package/dist/environment/Daylight.d.ts +56 -0
  58. package/dist/environment/Daylight.js +101 -0
  59. package/dist/environment/EnvironmentScene.js +87 -48
  60. package/dist/environment/GroundPad.d.ts +7 -0
  61. package/dist/environment/GroundPad.js +12 -0
  62. package/dist/environment/daylightCycle.d.ts +26 -0
  63. package/dist/environment/daylightCycle.js +116 -0
  64. package/dist/environment/groundPadMath.d.ts +13 -0
  65. package/dist/environment/groundPadMath.js +10 -0
  66. package/dist/environment/index.d.ts +3 -0
  67. package/dist/environment/index.js +3 -0
  68. package/dist/environment/skyLightingPolicy.d.ts +10 -0
  69. package/dist/environment/skyLightingPolicy.js +6 -0
  70. package/dist/input/mouseLook.d.ts +27 -0
  71. package/dist/input/mouseLook.js +35 -0
  72. package/dist/materialOverride.d.ts +6 -0
  73. package/dist/materialOverride.js +28 -0
  74. package/dist/multiplayer.d.ts +16 -9
  75. package/dist/multiplayer.js +62 -12
  76. package/dist/pointer/PointerProbe.js +20 -3
  77. package/dist/pointer/pointerService.d.ts +5 -0
  78. package/dist/pointer/pointerService.js +51 -24
  79. package/dist/registry.d.ts +4 -1
  80. package/dist/registry.js +3 -1
  81. package/dist/render/modelRender.d.ts +23 -0
  82. package/dist/render/modelRender.js +106 -0
  83. package/dist/render/resolveModel.d.ts +14 -0
  84. package/dist/render/resolveModel.js +24 -0
  85. package/dist/settings/QuickControls.d.ts +4 -0
  86. package/dist/settings/QuickControls.js +42 -0
  87. package/dist/settings/SettingsChrome.d.ts +1 -0
  88. package/dist/settings/SettingsChrome.js +10 -0
  89. package/dist/settings/SettingsMenu.d.ts +6 -0
  90. package/dist/settings/SettingsMenu.js +148 -0
  91. package/dist/settings/SettingsRuntime.d.ts +11 -0
  92. package/dist/settings/SettingsRuntime.js +19 -0
  93. package/dist/settings/appliedSettings.d.ts +14 -0
  94. package/dist/settings/appliedSettings.js +27 -0
  95. package/dist/settings/settingsController.d.ts +20 -0
  96. package/dist/settings/settingsController.js +165 -0
  97. package/dist/structures/GeneratedBuilding.d.ts +10 -0
  98. package/dist/structures/GeneratedBuilding.js +96 -8
  99. package/dist/structures/index.d.ts +1 -1
  100. package/dist/structures/index.js +1 -1
  101. package/dist/terrain/CarvedTerrain.d.ts +2 -1
  102. package/dist/terrain/CarvedTerrain.js +3 -3
  103. package/dist/terrain/GrassField.d.ts +3 -1
  104. package/dist/terrain/GrassField.js +4 -2
  105. package/dist/terrain/grassBudget.d.ts +3 -0
  106. package/dist/terrain/grassBudget.js +6 -0
  107. package/dist/terrain/grassGeometry.js +1 -1
  108. package/dist/terrain/index.d.ts +1 -1
  109. package/dist/terrain/index.js +1 -1
  110. package/dist/terrain/terrainMath.d.ts +9 -0
  111. package/dist/terrain/terrainMath.js +17 -2
  112. package/dist/touch/OrientationHint.d.ts +3 -0
  113. package/dist/touch/OrientationHint.js +13 -0
  114. package/dist/touch/TouchControlsOverlay.d.ts +34 -0
  115. package/dist/touch/TouchControlsOverlay.js +279 -0
  116. package/dist/visibility/CullingProvider.d.ts +21 -0
  117. package/dist/visibility/CullingProvider.js +134 -0
  118. package/dist/vision/FrustumSensorHud.d.ts +1 -6
  119. package/dist/vision/FrustumSensorHud.js +25 -27
  120. package/dist/vision/frustumSampleEqual.d.ts +2 -0
  121. package/dist/vision/frustumSampleEqual.js +10 -0
  122. package/dist/water/Ocean.js +1 -1
  123. package/dist/water/OceanConfig.d.ts +13 -0
  124. package/dist/water/OceanConfig.js +25 -17
  125. package/dist/water/OceanShader.d.ts +1 -1
  126. package/dist/water/OceanShader.js +3 -3
  127. package/dist/water/index.d.ts +1 -1
  128. package/dist/water/index.js +1 -1
  129. package/dist/weather/FireSpreadLayer.js +7 -2
  130. package/dist/weather/RainField.d.ts +3 -1
  131. package/dist/weather/RainField.js +4 -4
  132. package/dist/weather/SnowField.d.ts +3 -1
  133. package/dist/weather/SnowField.js +4 -4
  134. package/dist/weather/fireSpreadPose.d.ts +2 -0
  135. package/dist/weather/fireSpreadPose.js +4 -0
  136. package/dist/weather/weatherMath.d.ts +5 -1
  137. package/dist/weather/weatherMath.js +7 -2
  138. package/dist/world/DataObjects.d.ts +44 -0
  139. package/dist/world/DataObjects.js +77 -0
  140. package/dist/world/GridWorldScene.d.ts +10 -0
  141. package/dist/world/GridWorldScene.js +54 -0
  142. package/dist/world/SpriteBatch.d.ts +44 -0
  143. package/dist/world/SpriteBatch.js +112 -0
  144. package/dist/world/WorldHud.d.ts +8 -1
  145. package/dist/world/WorldHud.js +90 -39
  146. package/dist/world/WorldItems.js +21 -2
  147. package/dist/world/entityPose.d.ts +14 -0
  148. package/dist/world/entityPose.js +10 -0
  149. package/dist/world/telegraphPulse.d.ts +1 -0
  150. package/dist/world/telegraphPulse.js +4 -0
  151. package/dist/world/worldBarSamples.d.ts +30 -0
  152. package/dist/world/worldBarSamples.js +51 -0
  153. package/llms.txt +2643 -0
  154. package/package.json +6 -5
  155. package/dist/demo/builderDemo.d.ts +0 -2
  156. package/dist/demo/builderDemo.js +0 -189
  157. package/dist/demo/demoGame.d.ts +0 -2
  158. package/dist/demo/demoGame.js +0 -208
  159. package/dist/demo/environmentShowcase.d.ts +0 -2
  160. package/dist/demo/environmentShowcase.js +0 -15
  161. package/dist/demo/mapDemo.d.ts +0 -2
  162. package/dist/demo/mapDemo.js +0 -206
  163. package/dist/demo/pointerDemo.d.ts +0 -2
  164. package/dist/demo/pointerDemo.js +0 -151
  165. package/dist/demo/sensorShowcase.d.ts +0 -2
  166. package/dist/demo/sensorShowcase.js +0 -131
  167. package/dist/demo/survivalDemo.d.ts +0 -2
  168. package/dist/demo/survivalDemo.js +0 -291
@@ -1,5 +1,7 @@
1
1
  import * as THREE from "three";
2
2
  export const MAX_OCEAN_WAVES = 6;
3
+ /** Shared with `@jgengine/core/world/water` — primary wavelength in world units. */
4
+ export const DEFAULT_OCEAN_WAVE_SCALE = 18;
3
5
  export const OCEAN_QUALITY_PRESETS = {
4
6
  low: { size: 220, resolution: 80 },
5
7
  medium: { size: 360, resolution: 128 },
@@ -9,6 +11,7 @@ export const OCEAN_QUALITY_PRESETS = {
9
11
  export const DEFAULT_OCEAN_CONFIG = {
10
12
  quality: "medium",
11
13
  size: OCEAN_QUALITY_PRESETS.medium.size,
14
+ depth: OCEAN_QUALITY_PRESETS.medium.size,
12
15
  resolution: OCEAN_QUALITY_PRESETS.medium.resolution,
13
16
  amplitude: 1,
14
17
  speed: 1,
@@ -16,6 +19,7 @@ export const DEFAULT_OCEAN_CONFIG = {
16
19
  choppiness: 1,
17
20
  steepness: 0.72,
18
21
  timeScale: 1,
22
+ waveScale: DEFAULT_OCEAN_WAVE_SCALE,
19
23
  color: {
20
24
  shallow: "#2dc5d3",
21
25
  deep: "#064468",
@@ -33,14 +37,10 @@ export const DEFAULT_OCEAN_CONFIG = {
33
37
  },
34
38
  waves: [],
35
39
  };
36
- const DEFAULT_WAVE_SHAPE = [
37
- { amplitude: 1.15, wavelength: 44, speed: 1, steepness: 0.68 },
38
- { amplitude: 0.72, wavelength: 28, speed: 1.08, steepness: 0.58 },
39
- { amplitude: 0.44, wavelength: 17, speed: 1.18, steepness: 0.48 },
40
- { amplitude: 0.28, wavelength: 10, speed: 1.34, steepness: 0.38 },
41
- { amplitude: 0.18, wavelength: 6.5, speed: 1.52, steepness: 0.28 },
42
- { amplitude: 0.11, wavelength: 3.8, speed: 1.75, steepness: 0.18 },
43
- ];
40
+ const WAVE_WAVELENGTH_FALLOFF = 0.65;
41
+ const WAVE_AMPLITUDE_SHAPE = [1.15, 0.72, 0.44, 0.28, 0.18, 0.11];
42
+ const WAVE_SPEED_SHAPE = [1, 1.08, 1.18, 1.34, 1.52, 1.75];
43
+ const WAVE_STEEPNESS_SHAPE = [0.68, 0.58, 0.48, 0.38, 0.28, 0.18];
44
44
  const DEFAULT_DIRECTION_OFFSETS = [-8, 7, 21, -31, 46, -58];
45
45
  function finiteOr(value, fallback) {
46
46
  return value === undefined || !Number.isFinite(value) ? fallback : value;
@@ -67,18 +67,21 @@ function normalizeDirection(direction, fallback) {
67
67
  }
68
68
  function createDefaultWaves(config) {
69
69
  const baseAngle = directionAngle(config.direction);
70
- return DEFAULT_WAVE_SHAPE.map((wave, index) => ({
71
- amplitude: wave.amplitude,
72
- wavelength: wave.wavelength,
73
- speed: wave.speed,
70
+ return WAVE_AMPLITUDE_SHAPE.map((amplitude, index) => ({
71
+ amplitude,
72
+ wavelength: Math.max(0.1, config.waveScale * WAVE_WAVELENGTH_FALLOFF ** index),
73
+ speed: WAVE_SPEED_SHAPE[index],
74
74
  direction: directionFromAngle(baseAngle + DEFAULT_DIRECTION_OFFSETS[index]),
75
- steepness: wave.steepness,
75
+ steepness: WAVE_STEEPNESS_SHAPE[index],
76
76
  }));
77
77
  }
78
- function resolveWave(wave, fallback, config) {
78
+ function resolveWave(wave, fallback, config, customWaves) {
79
+ const wavelength = customWaves && wave?.wavelength !== undefined
80
+ ? wave.wavelength * (config.waveScale / DEFAULT_OCEAN_WAVE_SCALE)
81
+ : finiteOr(wave?.wavelength, fallback.wavelength);
79
82
  return {
80
83
  amplitude: Math.max(0, finiteOr(wave?.amplitude, fallback.amplitude) * config.amplitude),
81
- wavelength: Math.max(0.1, finiteOr(wave?.wavelength, fallback.wavelength)),
84
+ wavelength: Math.max(0.1, wavelength),
82
85
  speed: finiteOr(wave?.speed, fallback.speed) * config.speed,
83
86
  direction: normalizeDirection(wave?.direction, fallback.direction),
84
87
  steepness: clamp(finiteOr(wave?.steepness, fallback.steepness) * config.steepness, 0, 1.2),
@@ -87,9 +90,12 @@ function resolveWave(wave, fallback, config) {
87
90
  export function createOceanConfig(patch = {}) {
88
91
  const quality = patch.quality ?? DEFAULT_OCEAN_CONFIG.quality;
89
92
  const preset = OCEAN_QUALITY_PRESETS[quality];
93
+ const size = Math.max(1, finiteOr(patch.size, preset.size));
94
+ const depth = Math.max(1, finiteOr(patch.depth, size));
90
95
  const withoutWaves = {
91
96
  quality,
92
- size: Math.max(1, Math.floor(finiteOr(patch.size, preset.size))),
97
+ size,
98
+ depth,
93
99
  resolution: Math.max(1, Math.floor(finiteOr(patch.resolution, preset.resolution))),
94
100
  amplitude: Math.max(0, finiteOr(patch.amplitude, DEFAULT_OCEAN_CONFIG.amplitude)),
95
101
  speed: finiteOr(patch.speed, DEFAULT_OCEAN_CONFIG.speed),
@@ -97,6 +103,7 @@ export function createOceanConfig(patch = {}) {
97
103
  choppiness: Math.max(0, finiteOr(patch.choppiness, DEFAULT_OCEAN_CONFIG.choppiness)),
98
104
  steepness: Math.max(0, finiteOr(patch.steepness, DEFAULT_OCEAN_CONFIG.steepness)),
99
105
  timeScale: finiteOr(patch.timeScale, DEFAULT_OCEAN_CONFIG.timeScale),
106
+ waveScale: Math.max(0.1, finiteOr(patch.waveScale, DEFAULT_OCEAN_CONFIG.waveScale)),
100
107
  color: {
101
108
  ...DEFAULT_OCEAN_CONFIG.color,
102
109
  ...patch.color,
@@ -114,8 +121,9 @@ export function createOceanConfig(patch = {}) {
114
121
  },
115
122
  };
116
123
  const defaultWaves = createDefaultWaves(withoutWaves);
124
+ const customWaves = patch.waves !== undefined;
117
125
  const inputWaves = patch.waves ?? defaultWaves;
118
- const waves = Array.from({ length: MAX_OCEAN_WAVES }, (_, index) => resolveWave(inputWaves[index], defaultWaves[index], withoutWaves));
126
+ const waves = Array.from({ length: MAX_OCEAN_WAVES }, (_, index) => resolveWave(inputWaves[index], defaultWaves[index], withoutWaves, customWaves));
119
127
  return { ...withoutWaves, waves };
120
128
  }
121
129
  export function buildOceanWaveUniforms(config) {
@@ -1,2 +1,2 @@
1
- export declare const oceanVertexShader = "\nuniform float uTime;\nuniform vec2 uWaveDirections[6];\nuniform vec4 uWaveParams[6];\nuniform float uChoppiness;\nuniform float uFoamThreshold;\nuniform float uFoamSoftness;\nuniform float uFoamCoverage;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\nvoid main() {\n vec3 displaced = position;\n vec3 tangent = vec3(1.0, 0.0, 0.0);\n vec3 bitangent = vec3(0.0, 0.0, 1.0);\n float crest = 0.0;\n\n for (int i = 0; i < 6; i++) {\n vec2 direction = normalize(uWaveDirections[i]);\n vec4 wave = uWaveParams[i];\n float k = wave.x;\n float amplitude = wave.y;\n float steepness = wave.z;\n float omega = wave.w;\n float phase = k * dot(direction, position.xz) - omega * uTime;\n float sine = sin(phase);\n float cosine = cos(phase);\n float horizontal = steepness * amplitude * uChoppiness;\n displaced.x += horizontal * direction.x * cosine;\n displaced.z += horizontal * direction.y * cosine;\n displaced.y += amplitude * sine;\n float common = horizontal * k * sine;\n tangent += vec3(-common * direction.x * direction.x, amplitude * k * direction.x * cosine, -common * direction.x * direction.y);\n bitangent += vec3(-common * direction.x * direction.y, amplitude * k * direction.y * cosine, -common * direction.y * direction.y);\n crest = max(crest, smoothstep(uFoamThreshold, uFoamThreshold + uFoamSoftness, sine * steepness + uFoamCoverage * 0.35));\n }\n\n vNormal = normalize(cross(bitangent, tangent));\n vCrest = crest;\n vWaveHeight = displaced.y;\n vec4 worldPosition = modelMatrix * vec4(displaced, 1.0);\n vWorldPosition = worldPosition.xyz;\n gl_Position = projectionMatrix * viewMatrix * worldPosition;\n}\n";
1
+ export declare const oceanVertexShader = "\nuniform float uTime;\nuniform vec2 uWaveDirections[6];\nuniform vec4 uWaveParams[6];\nuniform float uChoppiness;\nuniform float uFoamThreshold;\nuniform float uFoamSoftness;\nuniform float uFoamCoverage;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\nvoid main() {\n vec3 displaced = position;\n vec3 tangent = vec3(1.0, 0.0, 0.0);\n vec3 bitangent = vec3(0.0, 0.0, 1.0);\n float crest = 0.0;\n\n for (int i = 0; i < 6; i++) {\n vec2 direction = normalize(uWaveDirections[i]);\n vec4 wave = uWaveParams[i];\n float k = wave.x;\n float amplitude = wave.y;\n float steepness = wave.z;\n float omega = wave.w;\n float phase = k * dot(direction, position.xz) - omega * uTime;\n float sine = sin(phase);\n float cosine = cos(phase);\n float horizontal = steepness * amplitude * uChoppiness;\n displaced.x += horizontal * direction.x * cosine;\n displaced.z += horizontal * direction.y * cosine;\n displaced.y += amplitude * sine;\n float slopeTerm = horizontal * k * sine;\n tangent += vec3(-slopeTerm * direction.x * direction.x, amplitude * k * direction.x * cosine, -slopeTerm * direction.x * direction.y);\n bitangent += vec3(-slopeTerm * direction.x * direction.y, amplitude * k * direction.y * cosine, -slopeTerm * direction.y * direction.y);\n crest = max(crest, smoothstep(uFoamThreshold, uFoamThreshold + uFoamSoftness, sine * steepness + uFoamCoverage * 0.35));\n }\n\n vNormal = normalize(cross(bitangent, tangent));\n vCrest = crest;\n vWaveHeight = displaced.y;\n vec4 worldPosition = modelMatrix * vec4(displaced, 1.0);\n vWorldPosition = worldPosition.xyz;\n gl_Position = projectionMatrix * viewMatrix * worldPosition;\n}\n";
2
2
  export declare const oceanFragmentShader = "\nuniform vec3 uShallowColor;\nuniform vec3 uDeepColor;\nuniform vec3 uCrestColor;\nuniform vec3 uFoamColor;\nuniform float uOpacity;\nuniform float uFresnelStrength;\nuniform float uHorizonBlend;\nuniform float uFoamIntensity;\n\nvarying vec3 vWorldPosition;\nvarying vec3 vNormal;\nvarying float vCrest;\nvarying float vWaveHeight;\n\nvoid main() {\n vec3 normal = normalize(vNormal);\n vec3 viewDirection = normalize(cameraPosition - vWorldPosition);\n float fresnel = pow(1.0 - max(dot(normal, viewDirection), 0.0), 4.5);\n float depthMix = smoothstep(-1.8, 1.6, vWaveHeight);\n vec3 waterColor = mix(uDeepColor, uShallowColor, depthMix);\n waterColor = mix(waterColor, uCrestColor, smoothstep(0.15, 1.2, vWaveHeight) * 0.28);\n waterColor = mix(waterColor, vec3(0.86, 0.95, 1.0), fresnel * uFresnelStrength);\n float horizon = smoothstep(0.0, 1.0, 1.0 - abs(normal.y));\n waterColor = mix(waterColor, uShallowColor, horizon * uHorizonBlend);\n float foam = clamp(vCrest * uFoamIntensity, 0.0, 1.0);\n vec3 color = mix(waterColor, uFoamColor, foam);\n gl_FragColor = vec4(color, uOpacity);\n}\n";
@@ -32,9 +32,9 @@ void main() {
32
32
  displaced.x += horizontal * direction.x * cosine;
33
33
  displaced.z += horizontal * direction.y * cosine;
34
34
  displaced.y += amplitude * sine;
35
- float common = horizontal * k * sine;
36
- tangent += vec3(-common * direction.x * direction.x, amplitude * k * direction.x * cosine, -common * direction.x * direction.y);
37
- bitangent += vec3(-common * direction.x * direction.y, amplitude * k * direction.y * cosine, -common * direction.y * direction.y);
35
+ float slopeTerm = horizontal * k * sine;
36
+ tangent += vec3(-slopeTerm * direction.x * direction.x, amplitude * k * direction.x * cosine, -slopeTerm * direction.x * direction.y);
37
+ bitangent += vec3(-slopeTerm * direction.x * direction.y, amplitude * k * direction.y * cosine, -slopeTerm * direction.y * direction.y);
38
38
  crest = max(crest, smoothstep(uFoamThreshold, uFoamThreshold + uFoamSoftness, sine * steepness + uFoamCoverage * 0.35));
39
39
  }
40
40
 
@@ -1,3 +1,3 @@
1
1
  export { Ocean, type OceanProps } from "./Ocean.js";
2
- export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, type OceanColorConfig, type OceanConfig, type OceanDirectionVector, type OceanFoamConfig, type OceanQualityPreset, type OceanWaveConfig, type OceanWaveDirection, type ResolvedOceanColorConfig, type ResolvedOceanConfig, type ResolvedOceanFoamConfig, type ResolvedOceanWaveConfig, } from "./OceanConfig.js";
2
+ export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, DEFAULT_OCEAN_WAVE_SCALE, type OceanColorConfig, type OceanConfig, type OceanDirectionVector, type OceanFoamConfig, type OceanQualityPreset, type OceanWaveConfig, type OceanWaveDirection, type ResolvedOceanColorConfig, type ResolvedOceanConfig, type ResolvedOceanFoamConfig, type ResolvedOceanWaveConfig, } from "./OceanConfig.js";
3
3
  export { createOceanMaterial, syncOceanMaterial, type OceanMaterialUniforms, type OceanShaderMaterial, } from "./OceanMaterial.js";
@@ -1,3 +1,3 @@
1
1
  export { Ocean } from "./Ocean.js";
2
- export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, } from "./OceanConfig.js";
2
+ export { DEFAULT_OCEAN_CONFIG, MAX_OCEAN_WAVES, OCEAN_QUALITY_PRESETS, buildOceanWaveUniforms, createOceanConfig, DEFAULT_OCEAN_WAVE_SCALE, } from "./OceanConfig.js";
3
3
  export { createOceanMaterial, syncOceanMaterial, } from "./OceanMaterial.js";
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef } from "react";
3
3
  import { useFrame } from "@react-three/fiber";
4
4
  import * as THREE from "three";
5
+ import { setBillboardQuaternion } from "./fireSpreadPose.js";
5
6
  const HIDDEN = new THREE.Matrix4().makeScale(0, 0, 0);
6
7
  export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, flameHeight = 1.6, burningColor = "#ff6a1a", emberColor = "#4a1206", }) {
7
8
  const flames = useRef(null);
@@ -24,6 +25,8 @@ export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, fla
24
25
  const matrix = useMemo(() => new THREE.Matrix4(), []);
25
26
  const position = useMemo(() => new THREE.Vector3(), []);
26
27
  const quaternion = useMemo(() => new THREE.Quaternion(), []);
28
+ const identityQuat = useMemo(() => new THREE.Quaternion(), []);
29
+ const euler = useMemo(() => new THREE.Euler(), []);
27
30
  const scale = useMemo(() => new THREE.Vector3(1, 1, 1), []);
28
31
  useFrame((state) => {
29
32
  const flameMesh = flames.current;
@@ -42,7 +45,7 @@ export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, fla
42
45
  const groundY = heightAt?.(x, z) ?? 0;
43
46
  if (cell.state === "burning") {
44
47
  position.set(x, groundY + flameHeight * 0.5, z);
45
- quaternion.setFromEuler(new THREE.Euler(0, state.camera.rotation.y, 0));
48
+ setBillboardQuaternion(quaternion, euler, state.camera.rotation.y);
46
49
  scale.set(1, flicker, 1);
47
50
  matrix.compose(position, quaternion, scale);
48
51
  flameMesh.setMatrixAt(flameIndex, matrix);
@@ -50,7 +53,9 @@ export function FireSpreadLayer({ grid, cellSize, origin = [0, 0], heightAt, fla
50
53
  }
51
54
  else if (cell.state === "burnt") {
52
55
  position.set(x, groundY + 0.05, z);
53
- scorchMesh.setMatrixAt(scorchIndex, matrix.compose(position, new THREE.Quaternion(), scale.set(1, 1, 1)));
56
+ scale.set(1, 1, 1);
57
+ matrix.compose(position, identityQuat, scale);
58
+ scorchMesh.setMatrixAt(scorchIndex, matrix);
54
59
  scorchIndex += 1;
55
60
  }
56
61
  }
@@ -3,6 +3,7 @@ import { type WeatherVector } from "./weatherUniforms.js";
3
3
  export interface RainFieldProps {
4
4
  count?: number;
5
5
  density?: number;
6
+ budget?: number;
6
7
  volume?: WeatherVector;
7
8
  wind?: WeatherVector;
8
9
  origin?: WeatherVector;
@@ -16,5 +17,6 @@ export interface RainFieldProps {
16
17
  timeScale?: number;
17
18
  seed?: number;
18
19
  renderOrder?: number;
20
+ frustumCulled?: boolean;
19
21
  }
20
- export declare function RainField({ count, density, volume, wind, origin, followCamera, speed, length, width, opacity, color, lightning, timeScale, seed, renderOrder, }: RainFieldProps): import("react").JSX.Element;
22
+ export declare function RainField({ count, density, budget, volume, wind, origin, followCamera, speed, length, width, opacity, color, lightning, timeScale, seed, renderOrder, frustumCulled, }: RainFieldProps): import("react").JSX.Element;
@@ -3,12 +3,12 @@ import { useEffect, useMemo } from "react";
3
3
  import { useFrame, useThree } from "@react-three/fiber";
4
4
  import * as THREE from "three";
5
5
  import { createWeatherQuadGeometry } from "./weatherGeometry.js";
6
- import { resolveWeatherInstanceCount } from "./weatherMath.js";
6
+ import { DEFAULT_RAIN_COUNT, DEFAULT_RAIN_DENSITY, resolveWeatherInstanceCount } from "./weatherMath.js";
7
7
  import { useWeatherUniformSet } from "./weatherUniforms.js";
8
8
  const DEFAULT_VOLUME = [56, 42, 56];
9
9
  const DEFAULT_ORIGIN = [0, 0, 0];
10
10
  const DEFAULT_RAIN_COLOR = "#b8c4d8";
11
- export function RainField({ count = 8000, density = 0.45, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 22, length = 1.35, width = 0.018, opacity = 0.48, color = DEFAULT_RAIN_COLOR, lightning, timeScale, seed = 11939, renderOrder = 10, }) {
11
+ export function RainField({ count = DEFAULT_RAIN_COUNT, density = DEFAULT_RAIN_DENSITY, budget, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 22, length = 1.35, width = 0.018, opacity = 0.48, color = DEFAULT_RAIN_COLOR, lightning, timeScale, seed = 11939, renderOrder = 10, frustumCulled, }) {
12
12
  const { camera } = useThree();
13
13
  const shared = useWeatherUniformSet({ wind, lightning, timeScale });
14
14
  const geometry = useMemo(() => createWeatherQuadGeometry(count, seed), [count, seed]);
@@ -97,11 +97,11 @@ export function RainField({ count = 8000, density = 0.45, volume = DEFAULT_VOLUM
97
97
  uniforms.uWidth.value = width;
98
98
  uniforms.uOpacity.value = opacity;
99
99
  uniforms.uColor.value.set(color);
100
- geometry.instanceCount = resolveWeatherInstanceCount(count, density);
100
+ geometry.instanceCount = resolveWeatherInstanceCount(count, density, budget);
101
101
  });
102
102
  useEffect(() => () => {
103
103
  geometry.dispose();
104
104
  material.dispose();
105
105
  }, [geometry, material]);
106
- return _jsx("mesh", { geometry: geometry, material: material, frustumCulled: false, renderOrder: renderOrder });
106
+ return (_jsx("mesh", { geometry: geometry, material: material, frustumCulled: frustumCulled ?? !followCamera, renderOrder: renderOrder }));
107
107
  }
@@ -3,6 +3,7 @@ import { type WeatherVector } from "./weatherUniforms.js";
3
3
  export interface SnowFieldProps {
4
4
  count?: number;
5
5
  density?: number;
6
+ budget?: number;
6
7
  volume?: WeatherVector;
7
8
  wind?: WeatherVector;
8
9
  origin?: WeatherVector;
@@ -15,5 +16,6 @@ export interface SnowFieldProps {
15
16
  timeScale?: number;
16
17
  seed?: number;
17
18
  renderOrder?: number;
19
+ frustumCulled?: boolean;
18
20
  }
19
- export declare function SnowField({ count, density, volume, wind, origin, followCamera, speed, size, sway, opacity, color, timeScale, seed, renderOrder, }: SnowFieldProps): import("react").JSX.Element;
21
+ export declare function SnowField({ count, density, budget, volume, wind, origin, followCamera, speed, size, sway, opacity, color, timeScale, seed, renderOrder, frustumCulled, }: SnowFieldProps): import("react").JSX.Element;
@@ -3,12 +3,12 @@ import { useEffect, useMemo } from "react";
3
3
  import { useFrame, useThree } from "@react-three/fiber";
4
4
  import * as THREE from "three";
5
5
  import { createWeatherQuadGeometry } from "./weatherGeometry.js";
6
- import { resolveWeatherInstanceCount } from "./weatherMath.js";
6
+ import { DEFAULT_SNOW_COUNT, DEFAULT_SNOW_DENSITY, resolveWeatherInstanceCount } from "./weatherMath.js";
7
7
  import { useWeatherUniformSet } from "./weatherUniforms.js";
8
8
  const DEFAULT_VOLUME = [52, 38, 52];
9
9
  const DEFAULT_ORIGIN = [0, 0, 0];
10
10
  const DEFAULT_SNOW_COLOR = "#ffffff";
11
- export function SnowField({ count = 6000, density = 0.5, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 3.2, size = 0.11, sway = 0.62, opacity = 0.86, color = DEFAULT_SNOW_COLOR, timeScale, seed = 72931, renderOrder = 11, }) {
11
+ export function SnowField({ count = DEFAULT_SNOW_COUNT, density = DEFAULT_SNOW_DENSITY, budget, volume = DEFAULT_VOLUME, wind, origin = DEFAULT_ORIGIN, followCamera = true, speed = 3.2, size = 0.11, sway = 0.62, opacity = 0.86, color = DEFAULT_SNOW_COLOR, timeScale, seed = 72931, renderOrder = 11, frustumCulled, }) {
12
12
  const { camera } = useThree();
13
13
  const shared = useWeatherUniformSet({ wind, timeScale });
14
14
  const geometry = useMemo(() => createWeatherQuadGeometry(count, seed), [count, seed]);
@@ -98,11 +98,11 @@ export function SnowField({ count = 6000, density = 0.5, volume = DEFAULT_VOLUME
98
98
  uniforms.uSway.value = sway;
99
99
  uniforms.uOpacity.value = opacity;
100
100
  uniforms.uColor.value.set(color);
101
- geometry.instanceCount = resolveWeatherInstanceCount(count, density);
101
+ geometry.instanceCount = resolveWeatherInstanceCount(count, density, budget);
102
102
  });
103
103
  useEffect(() => () => {
104
104
  geometry.dispose();
105
105
  material.dispose();
106
106
  }, [geometry, material]);
107
- return _jsx("mesh", { geometry: geometry, material: material, frustumCulled: false, renderOrder: renderOrder });
107
+ return (_jsx("mesh", { geometry: geometry, material: material, frustumCulled: frustumCulled ?? !followCamera, renderOrder: renderOrder }));
108
108
  }
@@ -0,0 +1,2 @@
1
+ import type { Euler, Quaternion } from "three";
2
+ export declare function setBillboardQuaternion(quaternion: Quaternion, euler: Euler, yaw: number): void;
@@ -0,0 +1,4 @@
1
+ export function setBillboardQuaternion(quaternion, euler, yaw) {
2
+ euler.set(0, yaw, 0);
3
+ quaternion.setFromEuler(euler);
4
+ }
@@ -1,5 +1,9 @@
1
+ export declare const DEFAULT_RAIN_COUNT = 2000;
2
+ export declare const DEFAULT_SNOW_COUNT = 1500;
3
+ export declare const DEFAULT_RAIN_DENSITY = 0.45;
4
+ export declare const DEFAULT_SNOW_DENSITY = 0.5;
1
5
  export declare function clampWeatherRatio(value: number): number;
2
- export declare function resolveWeatherInstanceCount(maxCount: number, density: number): number;
6
+ export declare function resolveWeatherInstanceCount(maxCount: number, density: number, budget?: number): number;
3
7
  export interface WeatherSeedAttributes {
4
8
  spawn: Float32Array;
5
9
  drift: Float32Array;
@@ -1,3 +1,7 @@
1
+ export const DEFAULT_RAIN_COUNT = 2000;
2
+ export const DEFAULT_SNOW_COUNT = 1500;
3
+ export const DEFAULT_RAIN_DENSITY = 0.45;
4
+ export const DEFAULT_SNOW_DENSITY = 0.5;
1
5
  export function clampWeatherRatio(value) {
2
6
  if (!Number.isFinite(value))
3
7
  return 0;
@@ -7,9 +11,10 @@ export function clampWeatherRatio(value) {
7
11
  return 1;
8
12
  return value;
9
13
  }
10
- export function resolveWeatherInstanceCount(maxCount, density) {
14
+ export function resolveWeatherInstanceCount(maxCount, density, budget) {
11
15
  const safeMax = Math.max(0, Math.floor(maxCount));
12
- return Math.floor(safeMax * clampWeatherRatio(density));
16
+ const capped = budget === undefined ? safeMax : Math.min(safeMax, Math.max(0, Math.floor(budget)));
17
+ return Math.floor(capped * clampWeatherRatio(density));
13
18
  }
14
19
  export function createWeatherSeedAttributes(maxCount, seed) {
15
20
  const count = Math.max(0, Math.floor(maxCount));
@@ -0,0 +1,44 @@
1
+ import { type ReactNode } from "react";
2
+ import * as THREE from "three";
3
+ /**
4
+ * Renders one placed 3D object per data item — a 3D bar chart, a heatmap, a city
5
+ * of buildings, a crowd. By default each item is an extruded box (all boxes share
6
+ * one `InstancedMesh`, so hundreds cost one draw call), sized and colored from the
7
+ * item; `renderItem` swaps the box for arbitrary content (a sprite, a GLB, a full
8
+ * entity). Genre-agnostic: the caller owns the data `T`, this owns the placement.
9
+ */
10
+ export interface DataObjectsProps<T> {
11
+ data: readonly T[];
12
+ /** World `[x, z]` for an item (placed on the ground plane). */
13
+ position: (item: T, index: number) => readonly [number, number];
14
+ /** Box height (Y-scale) for an item. */
15
+ height: (item: T, index: number) => number;
16
+ /** Box color for an item (any CSS/hex/THREE color). */
17
+ color: (item: T, index: number) => THREE.ColorRepresentation;
18
+ /** Footprint on X and Z. Default 0.28. */
19
+ cellSize?: number;
20
+ /** Index of the highlighted item, painted with `hoverColor`. */
21
+ hovered?: number | null;
22
+ hoverColor?: THREE.ColorRepresentation;
23
+ /** Called with the item index under the pointer, or null on exit. */
24
+ onHover?: (index: number | null) => void;
25
+ /** Staggered grow-in; restarts whenever `data` identity changes. */
26
+ grow?: {
27
+ duration?: number;
28
+ delay?: (item: T, index: number) => number;
29
+ };
30
+ castShadow?: boolean;
31
+ receiveShadow?: boolean;
32
+ /**
33
+ * Render arbitrary content per item instead of an instanced box — a sprite, a
34
+ * GLB, a full entity. The layout positions each on the ground at its `[x, z]`
35
+ * and hands you the resolved `height` to size against (e.g. one walking figure
36
+ * per unit of value). When set, the instanced-box path is bypassed.
37
+ */
38
+ renderItem?: (item: T, index: number, info: {
39
+ x: number;
40
+ z: number;
41
+ height: number;
42
+ }) => ReactNode;
43
+ }
44
+ export declare function DataObjects<T>({ data, position, height, color, cellSize, hovered, hoverColor, onHover, grow, castShadow, receiveShadow, renderItem, }: DataObjectsProps<T>): import("react").JSX.Element | null;
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useFrame } from "@react-three/fiber";
3
+ import { useEffect, useMemo, useRef } from "react";
4
+ import * as THREE from "three";
5
+ function clamp01(v) {
6
+ return v < 0 ? 0 : v > 1 ? 1 : v;
7
+ }
8
+ export function DataObjects({ data, position, height, color, cellSize = 0.28, hovered = null, hoverColor = "#ffffff", onHover, grow, castShadow = true, receiveShadow = true, renderItem, }) {
9
+ const meshRef = useRef(null);
10
+ const dummy = useMemo(() => new THREE.Object3D(), []);
11
+ const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
12
+ const material = useMemo(() => new THREE.MeshStandardMaterial({ roughness: 0.62, metalness: 0.04 }), []);
13
+ const hover = useMemo(() => new THREE.Color(hoverColor), [hoverColor]);
14
+ const startRef = useRef(null);
15
+ const capacity = data.length;
16
+ const layout = useMemo(() => {
17
+ const positions = new Float32Array(capacity * 2);
18
+ const targets = new Float32Array(capacity);
19
+ const delays = new Float32Array(capacity);
20
+ const colors = [];
21
+ data.forEach((item, i) => {
22
+ const [x, z] = position(item, i);
23
+ positions[i * 2] = x;
24
+ positions[i * 2 + 1] = z;
25
+ targets[i] = height(item, i);
26
+ delays[i] = grow?.delay?.(item, i) ?? 0;
27
+ colors.push(new THREE.Color(color(item, i)));
28
+ });
29
+ return { positions, targets, delays, colors };
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, [data]);
32
+ useEffect(() => {
33
+ startRef.current = null;
34
+ }, [data]);
35
+ useEffect(() => () => {
36
+ geometry.dispose();
37
+ material.dispose();
38
+ }, [geometry, material]);
39
+ const duration = grow?.duration ?? 0;
40
+ useFrame((state) => {
41
+ const mesh = meshRef.current;
42
+ if (mesh === null)
43
+ return;
44
+ if (startRef.current === null)
45
+ startRef.current = state.clock.elapsedTime;
46
+ const elapsed = state.clock.elapsedTime - startRef.current;
47
+ const { positions, targets, delays, colors } = layout;
48
+ for (let i = 0; i < capacity; i += 1) {
49
+ const grown = duration <= 0 ? 1 : clamp01((elapsed - delays[i]) / duration);
50
+ const eased = 1 - Math.pow(1 - grown, 3);
51
+ const h = Math.max(targets[i] * eased, 0.001);
52
+ dummy.position.set(positions[i * 2], h / 2, positions[i * 2 + 1]);
53
+ dummy.scale.set(cellSize, h, cellSize);
54
+ dummy.updateMatrix();
55
+ mesh.setMatrixAt(i, dummy.matrix);
56
+ mesh.setColorAt(i, i === hovered ? hover : colors[i]);
57
+ }
58
+ mesh.instanceMatrix.needsUpdate = true;
59
+ if (mesh.instanceColor !== null)
60
+ mesh.instanceColor.needsUpdate = true;
61
+ });
62
+ function handleMove(event) {
63
+ if (onHover === undefined)
64
+ return;
65
+ event.stopPropagation();
66
+ onHover(event.instanceId ?? null);
67
+ }
68
+ if (renderItem !== undefined) {
69
+ return (_jsx("group", { children: data.map((item, i) => {
70
+ const [x, z] = position(item, i);
71
+ return (_jsx("group", { position: [x, 0, z], children: renderItem(item, i, { x, z, height: height(item, i) }) }, i));
72
+ }) }));
73
+ }
74
+ if (capacity === 0)
75
+ return null;
76
+ return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, material, capacity], castShadow: castShadow, receiveShadow: receiveShadow, frustumCulled: false, onPointerMove: onHover === undefined ? undefined : handleMove, onPointerOut: onHover === undefined ? undefined : () => onHover(null) }, capacity));
77
+ }
@@ -0,0 +1,10 @@
1
+ import type { WorldFeature } from "@jgengine/core/world/features";
2
+ export interface GridWorldSceneProps {
3
+ feature: WorldFeature;
4
+ }
5
+ /**
6
+ * Data-driven renderer for the `biomes()`/`voxel()`/`plots()`/`tilemap()` world-feature kinds
7
+ * (#207.1): one `InstancedMesh` of extruded, colored boxes built from each feature's declared
8
+ * `cells`, following the same direct-buffer pattern as `InstancedBodies`.
9
+ */
10
+ export declare function GridWorldScene({ feature }: GridWorldSceneProps): import("react").JSX.Element | null;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef } from "react";
3
+ import * as THREE from "three";
4
+ import { resolveGridInstances } from "@jgengine/core/world/gridInstances";
5
+ function isGridWorldFeature(feature) {
6
+ return (feature.kind === "biomes" || feature.kind === "voxel" || feature.kind === "plots" || feature.kind === "tilemap");
7
+ }
8
+ /**
9
+ * Data-driven renderer for the `biomes()`/`voxel()`/`plots()`/`tilemap()` world-feature kinds
10
+ * (#207.1): one `InstancedMesh` of extruded, colored boxes built from each feature's declared
11
+ * `cells`, following the same direct-buffer pattern as `InstancedBodies`.
12
+ */
13
+ export function GridWorldScene({ feature }) {
14
+ const instances = useMemo(() => (isGridWorldFeature(feature) ? resolveGridInstances(feature) : []), [feature]);
15
+ const meshRef = useRef(null);
16
+ const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
17
+ const material = useMemo(() => new THREE.MeshStandardMaterial({ roughness: 0.9, metalness: 0 }), []);
18
+ const instanceColor = useMemo(() => new THREE.InstancedBufferAttribute(new Float32Array(Math.max(instances.length, 1) * 3), 3), [instances.length]);
19
+ useEffect(() => {
20
+ const mesh = meshRef.current;
21
+ if (mesh === null)
22
+ return;
23
+ mesh.instanceColor = instanceColor;
24
+ mesh.count = instances.length;
25
+ const m = mesh.instanceMatrix.array;
26
+ const c = instanceColor.array;
27
+ const color = new THREE.Color();
28
+ for (let i = 0; i < instances.length; i += 1) {
29
+ const instance = instances[i];
30
+ const o = i * 16;
31
+ m[o] = instance.scale[0];
32
+ m[o + 5] = instance.scale[1];
33
+ m[o + 10] = instance.scale[2];
34
+ m[o + 12] = instance.position[0];
35
+ m[o + 13] = instance.position[1];
36
+ m[o + 14] = instance.position[2];
37
+ m[o + 15] = 1;
38
+ color.set(instance.color);
39
+ const co = i * 3;
40
+ c[co] = color.r;
41
+ c[co + 1] = color.g;
42
+ c[co + 2] = color.b;
43
+ }
44
+ mesh.instanceMatrix.needsUpdate = true;
45
+ instanceColor.needsUpdate = true;
46
+ }, [instances, instanceColor]);
47
+ useEffect(() => () => {
48
+ geometry.dispose();
49
+ material.dispose();
50
+ }, [geometry, material]);
51
+ if (instances.length === 0)
52
+ return null;
53
+ return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, material, instances.length], castShadow: true, receiveShadow: true }));
54
+ }
@@ -0,0 +1,44 @@
1
+ export interface SpriteBatchInstance {
2
+ /** World X position. */
3
+ x: number;
4
+ /** World Y position. */
5
+ y: number;
6
+ /** World Z position. Defaults to 0. */
7
+ z?: number;
8
+ /** Atlas frame index, row-major from the sheet's top-left. Defaults to 0. */
9
+ frame?: number;
10
+ /** Uniform scale multiplier for the unit quad. Defaults to 1. */
11
+ scale?: number;
12
+ /** Rotation in radians about the plane's facing axis. Defaults to 0. */
13
+ rotation?: number;
14
+ }
15
+ export interface SpriteBatchProps {
16
+ /** Sprite sheet / atlas texture URL. */
17
+ url: string;
18
+ /** Atlas column count. Defaults to 1. */
19
+ columns?: number;
20
+ /** Atlas row count. Defaults to 1. */
21
+ rows?: number;
22
+ /** Max instances the InstancedMesh is allocated for. Defaults to 1024. */
23
+ capacity?: number;
24
+ /** Per-frame instance source, called once inside useFrame. */
25
+ instances: () => readonly SpriteBatchInstance[];
26
+ /** Facing plane when not billboarding: "xy" faces +Z (side-scroll/ortho), "xz" lies flat facing +Y. Defaults to "xy". */
27
+ plane?: "xy" | "xz";
28
+ /** When true, every instance orients to face the camera each frame, overriding `plane`. Defaults to false. */
29
+ billboard?: boolean;
30
+ /** Use nearest-neighbor filtering for crisp pixel art. Defaults to true. */
31
+ pixelated?: boolean;
32
+ /** Alpha test cutoff for transparent pixels. Defaults to 0.08. */
33
+ alphaTest?: number;
34
+ /** Material opacity. Defaults to 1. */
35
+ opacity?: number;
36
+ }
37
+ /**
38
+ * Renders a sprite sheet / tile atlas as a single InstancedMesh — one draw call for the whole
39
+ * batch. Each instance picks its atlas frame via a per-instance UV offset attribute patched into
40
+ * the material's vertex shader, so platformer/puzzle-grid presentation never needs one draw call
41
+ * per sprite. Transforms and UV offsets are written directly into the mesh's typed arrays each
42
+ * frame from a plain instance list (bodies never touch the per-entity React path).
43
+ */
44
+ export declare function SpriteBatch({ url, columns, rows, capacity, instances, plane, billboard, pixelated, alphaTest, opacity, }: SpriteBatchProps): import("react").JSX.Element;