@holoscript/engine 6.0.3 → 6.0.4

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 (192) hide show
  1. package/dist/AutoMesher-CK47F6AV.js +17 -0
  2. package/dist/GPUBuffers-2LHBCD7X.js +9 -0
  3. package/dist/WebGPUContext-TNEUYU2Y.js +11 -0
  4. package/dist/animation/index.cjs +38 -38
  5. package/dist/animation/index.d.cts +1 -1
  6. package/dist/animation/index.d.ts +1 -1
  7. package/dist/animation/index.js +1 -1
  8. package/dist/audio/index.cjs +16 -6
  9. package/dist/audio/index.d.cts +1 -1
  10. package/dist/audio/index.d.ts +1 -1
  11. package/dist/audio/index.js +1 -1
  12. package/dist/camera/index.cjs +23 -23
  13. package/dist/camera/index.d.cts +1 -1
  14. package/dist/camera/index.d.ts +1 -1
  15. package/dist/camera/index.js +1 -1
  16. package/dist/character/index.cjs +6 -4
  17. package/dist/character/index.js +1 -1
  18. package/dist/choreography/index.cjs +1194 -0
  19. package/dist/choreography/index.d.cts +687 -0
  20. package/dist/choreography/index.d.ts +687 -0
  21. package/dist/choreography/index.js +1156 -0
  22. package/dist/chunk-2CSNRI2N.js +217 -0
  23. package/dist/chunk-33T2WINR.js +266 -0
  24. package/dist/chunk-35R73OFM.js +1257 -0
  25. package/dist/chunk-4MMDSUNP.js +1256 -0
  26. package/dist/chunk-5V6HOU72.js +319 -0
  27. package/dist/chunk-6QOP6PYF.js +1038 -0
  28. package/dist/chunk-7KMJVHIL.js +8944 -0
  29. package/dist/chunk-7VPUC62U.js +1106 -0
  30. package/dist/chunk-A2Y6RCAT.js +1878 -0
  31. package/dist/chunk-AHM42MK6.js +8944 -0
  32. package/dist/chunk-BL7IDTHE.js +218 -0
  33. package/dist/chunk-CITOMSWL.js +10462 -0
  34. package/dist/chunk-CXDPKW2K.js +8944 -0
  35. package/dist/chunk-CXZPLD4S.js +223 -0
  36. package/dist/chunk-CZYJE7IH.js +5169 -0
  37. package/dist/chunk-D2OP7YC7.js +6325 -0
  38. package/dist/chunk-EDRVQHUU.js +1544 -0
  39. package/dist/chunk-EJSLOOW2.js +3589 -0
  40. package/dist/chunk-F53SFGW5.js +1878 -0
  41. package/dist/chunk-HCFPELPY.js +919 -0
  42. package/dist/chunk-HNEE36PY.js +93 -0
  43. package/dist/chunk-HYXNV36F.js +1256 -0
  44. package/dist/chunk-IB7KHVFY.js +821 -0
  45. package/dist/chunk-IBBO7YYG.js +690 -0
  46. package/dist/chunk-ILIBGINU.js +5470 -0
  47. package/dist/chunk-IS4MHLKN.js +5479 -0
  48. package/dist/chunk-JT2PFKWD.js +5479 -0
  49. package/dist/chunk-K4CUB4NY.js +1038 -0
  50. package/dist/chunk-KATDQXRJ.js +10462 -0
  51. package/dist/chunk-KBQE6ZFJ.js +8944 -0
  52. package/dist/chunk-KBVD5K7E.js +560 -0
  53. package/dist/chunk-KCDPVQRY.js +4088 -0
  54. package/dist/chunk-KN4QJPKN.js +8944 -0
  55. package/dist/chunk-KWJ3ROSI.js +8944 -0
  56. package/dist/chunk-L45VF6DD.js +919 -0
  57. package/dist/chunk-LY4T37YK.js +307 -0
  58. package/dist/chunk-MDN5WZXA.js +1544 -0
  59. package/dist/chunk-MGCDP6VU.js +928 -0
  60. package/dist/chunk-NCX7X6G2.js +8681 -0
  61. package/dist/chunk-OF54BPVD.js +913 -0
  62. package/dist/chunk-OWSN2Q3Q.js +690 -0
  63. package/dist/chunk-PRRB5TTA.js +406 -0
  64. package/dist/chunk-PXWVQF76.js +4086 -0
  65. package/dist/chunk-PYCOIDT2.js +812 -0
  66. package/dist/chunk-PZCSADOV.js +928 -0
  67. package/dist/chunk-Q2XBVS2K.js +1038 -0
  68. package/dist/chunk-QDZRXWN5.js +1776 -0
  69. package/dist/chunk-RNWOZ6WQ.js +913 -0
  70. package/dist/chunk-ROLFT4CJ.js +1693 -0
  71. package/dist/chunk-SLTJRZ2N.js +266 -0
  72. package/dist/chunk-SRUS5XSU.js +4088 -0
  73. package/dist/chunk-TKCA3WZ5.js +5409 -0
  74. package/dist/chunk-TNRMXYI2.js +1650 -0
  75. package/dist/chunk-TQB3GJGM.js +9763 -0
  76. package/dist/chunk-TUFGXG6K.js +510 -0
  77. package/dist/chunk-U6KMTGQJ.js +632 -0
  78. package/dist/chunk-VMGJQST6.js +8681 -0
  79. package/dist/chunk-X4F4TCG4.js +5470 -0
  80. package/dist/chunk-ZIFROE75.js +1544 -0
  81. package/dist/chunk-ZIJQYHSQ.js +1204 -0
  82. package/dist/combat/index.cjs +4 -4
  83. package/dist/combat/index.d.cts +1 -1
  84. package/dist/combat/index.d.ts +1 -1
  85. package/dist/combat/index.js +1 -1
  86. package/dist/ecs/index.cjs +1 -1
  87. package/dist/ecs/index.js +1 -1
  88. package/dist/environment/index.cjs +14 -14
  89. package/dist/environment/index.d.cts +1 -1
  90. package/dist/environment/index.d.ts +1 -1
  91. package/dist/environment/index.js +1 -1
  92. package/dist/gpu/index.cjs +4810 -0
  93. package/dist/gpu/index.js +3714 -0
  94. package/dist/hologram/index.cjs +27 -1
  95. package/dist/hologram/index.js +1 -1
  96. package/dist/index-B2PIsAmR.d.cts +2180 -0
  97. package/dist/index-B2PIsAmR.d.ts +2180 -0
  98. package/dist/index-BHySEPX7.d.cts +2921 -0
  99. package/dist/index-BJV21zuy.d.cts +341 -0
  100. package/dist/index-BJV21zuy.d.ts +341 -0
  101. package/dist/index-BQutTphC.d.cts +790 -0
  102. package/dist/index-ByIq2XrS.d.cts +3910 -0
  103. package/dist/index-BysHjDSO.d.cts +224 -0
  104. package/dist/index-BysHjDSO.d.ts +224 -0
  105. package/dist/index-CKwAJGck.d.ts +455 -0
  106. package/dist/index-CUl3QstQ.d.cts +3006 -0
  107. package/dist/index-CUl3QstQ.d.ts +3006 -0
  108. package/dist/index-CmYtNiI-.d.cts +953 -0
  109. package/dist/index-CmYtNiI-.d.ts +953 -0
  110. package/dist/index-CnRzWxi_.d.cts +522 -0
  111. package/dist/index-CnRzWxi_.d.ts +522 -0
  112. package/dist/index-CwRWbSC7.d.ts +2921 -0
  113. package/dist/index-CxKIBstO.d.ts +790 -0
  114. package/dist/index-DJ6-R8vh.d.cts +455 -0
  115. package/dist/index-DQKisbcI.d.cts +4968 -0
  116. package/dist/index-DQKisbcI.d.ts +4968 -0
  117. package/dist/index-DRT2zJez.d.ts +3910 -0
  118. package/dist/index-DfNLiAka.d.cts +192 -0
  119. package/dist/index-DfNLiAka.d.ts +192 -0
  120. package/dist/index-nMvkoRm8.d.cts +405 -0
  121. package/dist/index-nMvkoRm8.d.ts +405 -0
  122. package/dist/index-s9yOFU37.d.cts +604 -0
  123. package/dist/index-s9yOFU37.d.ts +604 -0
  124. package/dist/index.cjs +22966 -6960
  125. package/dist/index.d.cts +864 -20
  126. package/dist/index.d.ts +864 -20
  127. package/dist/index.js +3062 -48
  128. package/dist/input/index.cjs +1 -1
  129. package/dist/input/index.js +1 -1
  130. package/dist/orbital/index.cjs +3 -3
  131. package/dist/orbital/index.d.cts +1 -1
  132. package/dist/orbital/index.d.ts +1 -1
  133. package/dist/orbital/index.js +1 -1
  134. package/dist/particles/index.cjs +16 -16
  135. package/dist/particles/index.d.cts +1 -1
  136. package/dist/particles/index.d.ts +1 -1
  137. package/dist/particles/index.js +1 -1
  138. package/dist/physics/index.cjs +2377 -21
  139. package/dist/physics/index.d.cts +1 -1
  140. package/dist/physics/index.d.ts +1 -1
  141. package/dist/physics/index.js +35 -1
  142. package/dist/postfx/index.cjs +3491 -0
  143. package/dist/postfx/index.js +93 -0
  144. package/dist/procedural/index.cjs +1 -1
  145. package/dist/procedural/index.js +1 -1
  146. package/dist/puppeteer-5VF6KDVO.js +52197 -0
  147. package/dist/puppeteer-IZVZ3SG4.js +52197 -0
  148. package/dist/rendering/index.cjs +33 -32
  149. package/dist/rendering/index.d.cts +1 -1
  150. package/dist/rendering/index.d.ts +1 -1
  151. package/dist/rendering/index.js +8 -6
  152. package/dist/runtime/index.cjs +23 -13
  153. package/dist/runtime/index.d.cts +1 -1
  154. package/dist/runtime/index.d.ts +1 -1
  155. package/dist/runtime/index.js +8 -6
  156. package/dist/runtime/protocols/index.cjs +349 -0
  157. package/dist/runtime/protocols/index.js +15 -0
  158. package/dist/scene/index.cjs +8 -8
  159. package/dist/scene/index.d.cts +1 -1
  160. package/dist/scene/index.d.ts +1 -1
  161. package/dist/scene/index.js +1 -1
  162. package/dist/shader/index.cjs +3087 -0
  163. package/dist/shader/index.js +3044 -0
  164. package/dist/simulation/index.cjs +10680 -0
  165. package/dist/simulation/index.d.cts +3 -0
  166. package/dist/simulation/index.d.ts +3 -0
  167. package/dist/simulation/index.js +307 -0
  168. package/dist/spatial/index.cjs +2443 -0
  169. package/dist/spatial/index.d.cts +1545 -0
  170. package/dist/spatial/index.d.ts +1545 -0
  171. package/dist/spatial/index.js +2400 -0
  172. package/dist/terrain/index.cjs +1 -1
  173. package/dist/terrain/index.d.cts +1 -1
  174. package/dist/terrain/index.d.ts +1 -1
  175. package/dist/terrain/index.js +1 -1
  176. package/dist/transformers.node-4NKAPD5U.js +45620 -0
  177. package/dist/vm/index.cjs +7 -8
  178. package/dist/vm/index.d.cts +1 -1
  179. package/dist/vm/index.d.ts +1 -1
  180. package/dist/vm/index.js +1 -1
  181. package/dist/vm-bridge/index.cjs +2 -2
  182. package/dist/vm-bridge/index.d.cts +2 -2
  183. package/dist/vm-bridge/index.d.ts +2 -2
  184. package/dist/vm-bridge/index.js +1 -1
  185. package/dist/vr/index.cjs +6 -6
  186. package/dist/vr/index.js +1 -1
  187. package/dist/world/index.cjs +3 -3
  188. package/dist/world/index.d.cts +1 -1
  189. package/dist/world/index.d.ts +1 -1
  190. package/dist/world/index.js +1 -1
  191. package/package.json +53 -21
  192. package/LICENSE +0 -21
@@ -0,0 +1,1544 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-AKLW2MUS.js";
4
+
5
+ // src/environment/index.ts
6
+ var environment_exports = {};
7
+ __export(environment_exports, {
8
+ ALL_PRESETS: () => ALL_PRESETS,
9
+ DayNightCycle: () => DayNightCycle,
10
+ EnvironmentManager: () => EnvironmentManager,
11
+ FoliageSystem: () => FoliageSystem,
12
+ GrassRenderer: () => GrassRenderer,
13
+ PRESET_NIGHT: () => PRESET_NIGHT,
14
+ PRESET_OVERCAST: () => PRESET_OVERCAST,
15
+ PRESET_SCIFI: () => PRESET_SCIFI,
16
+ PRESET_SUNNY_DAY: () => PRESET_SUNNY_DAY,
17
+ PRESET_SUNSET: () => PRESET_SUNSET,
18
+ SkyRenderer: () => SkyRenderer,
19
+ TerrainBrush: () => TerrainBrush,
20
+ TerrainSystem: () => TerrainSystem,
21
+ TreePlacer: () => TreePlacer,
22
+ WeatherSystem: () => WeatherSystem,
23
+ computeSunIntensity: () => computeSunIntensity,
24
+ computeSunPosition: () => computeSunPosition,
25
+ resetWeatherBlackboard: () => resetWeatherBlackboard,
26
+ updateWeatherBlackboard: () => updateWeatherBlackboard,
27
+ weatherBlackboard: () => weatherBlackboard
28
+ });
29
+
30
+ // src/environment/DayNightCycle.ts
31
+ var DayNightCycle = class {
32
+ time = 8;
33
+ // Start at 8 AM
34
+ timeScale = 1;
35
+ // 1 = real-time, 60 = 1 min per real second
36
+ dayCount = 0;
37
+ paused = false;
38
+ listeners = [];
39
+ lastPeriod = "morning";
40
+ // ---------------------------------------------------------------------------
41
+ // Time Control
42
+ // ---------------------------------------------------------------------------
43
+ setTime(hours) {
44
+ this.time = (hours % 24 + 24) % 24;
45
+ this.lastPeriod = this.getPeriod();
46
+ }
47
+ setTimeScale(scale) {
48
+ this.timeScale = Math.max(0, scale);
49
+ }
50
+ getTimeScale() {
51
+ return this.timeScale;
52
+ }
53
+ pause() {
54
+ this.paused = true;
55
+ }
56
+ resume() {
57
+ this.paused = false;
58
+ }
59
+ isPaused() {
60
+ return this.paused;
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Update
64
+ // ---------------------------------------------------------------------------
65
+ update(dt) {
66
+ if (this.paused) return;
67
+ const hourStep = dt * this.timeScale / 3600;
68
+ this.time += hourStep;
69
+ while (this.time >= 24) {
70
+ this.time -= 24;
71
+ this.dayCount++;
72
+ }
73
+ const newPeriod = this.getPeriod();
74
+ if (newPeriod !== this.lastPeriod) {
75
+ this.lastPeriod = newPeriod;
76
+ for (const l of this.listeners) l(newPeriod, this.getState());
77
+ }
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Sun / Moon
81
+ // ---------------------------------------------------------------------------
82
+ getSunAngle() {
83
+ if (this.time >= 6 && this.time <= 18) {
84
+ return (this.time - 6) / 12 * 180;
85
+ }
86
+ return -1;
87
+ }
88
+ getMoonAngle() {
89
+ if (this.time >= 18 || this.time <= 6) {
90
+ const moonTime = this.time >= 18 ? this.time - 18 : this.time + 6;
91
+ return moonTime / 12 * 180;
92
+ }
93
+ return -1;
94
+ }
95
+ getSunIntensity() {
96
+ const angle = this.getSunAngle();
97
+ if (angle < 0) return 0;
98
+ return Math.sin(angle / 180 * Math.PI);
99
+ }
100
+ getMoonIntensity() {
101
+ const angle = this.getMoonAngle();
102
+ if (angle < 0) return 0;
103
+ return Math.sin(angle / 180 * Math.PI) * 0.3;
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Ambient
107
+ // ---------------------------------------------------------------------------
108
+ getAmbientColor() {
109
+ const intensity = this.getSunIntensity();
110
+ if (intensity > 0.7) return { r: 1, g: 0.95, b: 0.9 };
111
+ if (intensity > 0.3) return { r: 1, g: 0.7, b: 0.4 };
112
+ if (intensity > 0) return { r: 0.6, g: 0.3, b: 0.3 };
113
+ return { r: 0.1, g: 0.1, b: 0.2 };
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // Period
117
+ // ---------------------------------------------------------------------------
118
+ getPeriod() {
119
+ if (this.time >= 5 && this.time < 7) return "dawn";
120
+ if (this.time >= 7 && this.time < 11) return "morning";
121
+ if (this.time >= 11 && this.time < 13) return "noon";
122
+ if (this.time >= 13 && this.time < 17) return "afternoon";
123
+ if (this.time >= 17 && this.time < 19) return "dusk";
124
+ if (this.time >= 19 && this.time < 22) return "evening";
125
+ if (this.time >= 22 || this.time < 1) return "night";
126
+ return "midnight";
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Events
130
+ // ---------------------------------------------------------------------------
131
+ onPeriodChange(listener) {
132
+ this.listeners.push(listener);
133
+ }
134
+ // ---------------------------------------------------------------------------
135
+ // Queries
136
+ // ---------------------------------------------------------------------------
137
+ getTime() {
138
+ return this.time;
139
+ }
140
+ getDayCount() {
141
+ return this.dayCount;
142
+ }
143
+ getState() {
144
+ return {
145
+ time: this.time,
146
+ sunAngle: this.getSunAngle(),
147
+ moonAngle: this.getMoonAngle(),
148
+ sunIntensity: this.getSunIntensity(),
149
+ moonIntensity: this.getMoonIntensity(),
150
+ ambientColor: this.getAmbientColor(),
151
+ ambientIntensity: Math.max(this.getSunIntensity(), this.getMoonIntensity()),
152
+ period: this.getPeriod(),
153
+ dayCount: this.dayCount
154
+ };
155
+ }
156
+ getFormattedTime() {
157
+ const h = Math.floor(this.time);
158
+ const m = Math.floor(this.time % 1 * 60);
159
+ return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`;
160
+ }
161
+ };
162
+
163
+ // src/environment/EnvironmentPresets.ts
164
+ var PRESET_SUNNY_DAY = {
165
+ id: "sunny_day",
166
+ name: "Sunny Day",
167
+ skybox: {
168
+ type: "gradient",
169
+ topColor: "#1e90ff",
170
+ bottomColor: "#87ceeb",
171
+ horizonColor: "#f0f8ff",
172
+ sunPosition: { x: 0.5, y: 0.8, z: 0.2 }
173
+ },
174
+ lights: [
175
+ {
176
+ type: "directional",
177
+ color: "#fff5e6",
178
+ intensity: 1.2,
179
+ direction: { x: -0.5, y: -0.8, z: -0.2 },
180
+ castShadows: true,
181
+ shadowMapSize: 2048
182
+ },
183
+ { type: "ambient", color: "#87ceeb", intensity: 0.4 }
184
+ ],
185
+ fog: { type: "exponential2", color: "#c8dff5", density: 8e-4 },
186
+ atmosphere: {
187
+ ambientOcclusion: true,
188
+ bloom: true,
189
+ bloomIntensity: 0.5,
190
+ bloomThreshold: 0.8,
191
+ toneMappingExposure: 1,
192
+ colorGrading: { saturation: 1.1, contrast: 1.05, temperature: 6500 }
193
+ }
194
+ };
195
+ var PRESET_SUNSET = {
196
+ id: "sunset",
197
+ name: "Golden Sunset",
198
+ skybox: {
199
+ type: "gradient",
200
+ topColor: "#1a0533",
201
+ bottomColor: "#ff6b35",
202
+ horizonColor: "#ff4500",
203
+ sunPosition: { x: 0.9, y: 0.1, z: 0 }
204
+ },
205
+ lights: [
206
+ {
207
+ type: "directional",
208
+ color: "#ff8c42",
209
+ intensity: 0.8,
210
+ direction: { x: -0.9, y: -0.1, z: 0 },
211
+ castShadows: true,
212
+ shadowMapSize: 2048
213
+ },
214
+ { type: "ambient", color: "#4a2040", intensity: 0.3 }
215
+ ],
216
+ fog: { type: "exponential2", color: "#ff8c6b", density: 1e-3 },
217
+ atmosphere: {
218
+ ambientOcclusion: true,
219
+ bloom: true,
220
+ bloomIntensity: 1,
221
+ bloomThreshold: 0.6,
222
+ toneMappingExposure: 1.2,
223
+ colorGrading: { saturation: 1.3, contrast: 1.1, temperature: 4e3 }
224
+ }
225
+ };
226
+ var PRESET_NIGHT = {
227
+ id: "night",
228
+ name: "Moonlit Night",
229
+ skybox: {
230
+ type: "gradient",
231
+ topColor: "#0a0a1a",
232
+ bottomColor: "#14142b",
233
+ horizonColor: "#1a1a3e",
234
+ sunPosition: { x: -0.3, y: 0.6, z: 0.5 }
235
+ // Moon position
236
+ },
237
+ lights: [
238
+ {
239
+ type: "directional",
240
+ color: "#b0c4de",
241
+ intensity: 0.15,
242
+ direction: { x: 0.3, y: -0.6, z: -0.5 },
243
+ castShadows: true,
244
+ shadowMapSize: 1024
245
+ },
246
+ { type: "ambient", color: "#0a0a2e", intensity: 0.1 }
247
+ ],
248
+ fog: { type: "exponential2", color: "#0a0a1a", density: 2e-3 },
249
+ atmosphere: {
250
+ ambientOcclusion: true,
251
+ bloom: true,
252
+ bloomIntensity: 0.8,
253
+ bloomThreshold: 0.4,
254
+ toneMappingExposure: 0.5,
255
+ colorGrading: { saturation: 0.7, contrast: 1.2, temperature: 8e3 }
256
+ }
257
+ };
258
+ var PRESET_OVERCAST = {
259
+ id: "overcast",
260
+ name: "Overcast",
261
+ skybox: {
262
+ type: "gradient",
263
+ topColor: "#808890",
264
+ bottomColor: "#a0a8b0",
265
+ horizonColor: "#b0b8c0"
266
+ },
267
+ lights: [
268
+ {
269
+ type: "directional",
270
+ color: "#c0c0c0",
271
+ intensity: 0.5,
272
+ direction: { x: -0.3, y: -0.8, z: -0.2 },
273
+ castShadows: true,
274
+ shadowMapSize: 1024
275
+ },
276
+ { type: "ambient", color: "#909090", intensity: 0.5 }
277
+ ],
278
+ fog: { type: "exponential2", color: "#a0a8b0", density: 15e-4 },
279
+ atmosphere: {
280
+ ambientOcclusion: true,
281
+ bloom: false,
282
+ bloomIntensity: 0,
283
+ bloomThreshold: 1,
284
+ toneMappingExposure: 0.8,
285
+ colorGrading: { saturation: 0.8, contrast: 0.95, temperature: 6800 }
286
+ }
287
+ };
288
+ var PRESET_SCIFI = {
289
+ id: "scifi",
290
+ name: "Sci-Fi Neon",
291
+ skybox: {
292
+ type: "gradient",
293
+ topColor: "#0a001a",
294
+ bottomColor: "#1a0033",
295
+ horizonColor: "#330066"
296
+ },
297
+ lights: [
298
+ {
299
+ type: "directional",
300
+ color: "#00ffff",
301
+ intensity: 0.3,
302
+ direction: { x: 0, y: -1, z: 0 },
303
+ castShadows: true,
304
+ shadowMapSize: 2048
305
+ },
306
+ { type: "ambient", color: "#1a003a", intensity: 0.2 },
307
+ { type: "point", color: "#ff00ff", intensity: 2, position: { x: 0, y: 10, z: 0 } }
308
+ ],
309
+ fog: { type: "exponential2", color: "#0a001a", density: 3e-3 },
310
+ atmosphere: {
311
+ ambientOcclusion: true,
312
+ bloom: true,
313
+ bloomIntensity: 2,
314
+ bloomThreshold: 0.3,
315
+ toneMappingExposure: 1.5,
316
+ colorGrading: { saturation: 1.5, contrast: 1.3, temperature: 3500 }
317
+ }
318
+ };
319
+ var ALL_PRESETS = [
320
+ PRESET_SUNNY_DAY,
321
+ PRESET_SUNSET,
322
+ PRESET_NIGHT,
323
+ PRESET_OVERCAST,
324
+ PRESET_SCIFI
325
+ ];
326
+ var EnvironmentManager = class {
327
+ currentEnv;
328
+ timeOfDay;
329
+ weather;
330
+ constructor(preset = PRESET_SUNNY_DAY) {
331
+ this.currentEnv = { ...preset };
332
+ this.timeOfDay = {
333
+ sunriseHour: 6,
334
+ sunsetHour: 18,
335
+ currentHour: 12,
336
+ daySpeed: 1
337
+ };
338
+ this.weather = {
339
+ type: "clear",
340
+ intensity: 0,
341
+ windDirection: { x: 1, y: 0, z: 0 },
342
+ windSpeed: 2,
343
+ transitionProgress: 1
344
+ };
345
+ }
346
+ // ---------------------------------------------------------------------------
347
+ // Environment
348
+ // ---------------------------------------------------------------------------
349
+ setEnvironment(config) {
350
+ this.currentEnv = { ...config };
351
+ }
352
+ getEnvironment() {
353
+ return { ...this.currentEnv };
354
+ }
355
+ setPreset(presetId) {
356
+ const preset = ALL_PRESETS.find((p) => p.id === presetId);
357
+ if (!preset) return false;
358
+ this.currentEnv = { ...preset };
359
+ return true;
360
+ }
361
+ getPresetIds() {
362
+ return ALL_PRESETS.map((p) => p.id);
363
+ }
364
+ // ---------------------------------------------------------------------------
365
+ // Time of Day
366
+ // ---------------------------------------------------------------------------
367
+ setTimeOfDay(hour) {
368
+ this.timeOfDay.currentHour = hour % 24;
369
+ this.updateSunFromTime();
370
+ }
371
+ advanceTime(deltaSeconds) {
372
+ const hoursElapsed = deltaSeconds / 3600 * this.timeOfDay.daySpeed;
373
+ this.timeOfDay.currentHour = (this.timeOfDay.currentHour + hoursElapsed) % 24;
374
+ this.updateSunFromTime();
375
+ }
376
+ getTimeOfDay() {
377
+ return { ...this.timeOfDay };
378
+ }
379
+ updateSunFromTime() {
380
+ const hour = this.timeOfDay.currentHour;
381
+ const dayLength = this.timeOfDay.sunsetHour - this.timeOfDay.sunriseHour;
382
+ const isDay = hour >= this.timeOfDay.sunriseHour && hour <= this.timeOfDay.sunsetHour;
383
+ if (isDay) {
384
+ const progress = (hour - this.timeOfDay.sunriseHour) / dayLength;
385
+ const angle = progress * Math.PI;
386
+ this.currentEnv.skybox.sunPosition = {
387
+ x: Math.cos(angle) * 0.5,
388
+ y: Math.sin(angle),
389
+ z: 0.2
390
+ };
391
+ const sunIntensity = Math.sin(angle);
392
+ if (this.currentEnv.lights[0]) {
393
+ this.currentEnv.lights[0].intensity = 0.3 + sunIntensity * 0.9;
394
+ }
395
+ } else {
396
+ this.currentEnv.skybox.sunPosition = { x: 0, y: -0.5, z: 0 };
397
+ if (this.currentEnv.lights[0]) {
398
+ this.currentEnv.lights[0].intensity = 0.1;
399
+ }
400
+ }
401
+ }
402
+ // ---------------------------------------------------------------------------
403
+ // Weather
404
+ // ---------------------------------------------------------------------------
405
+ setWeather(type, intensity = 0.5) {
406
+ this.weather = {
407
+ ...this.weather,
408
+ type,
409
+ intensity: Math.max(0, Math.min(1, intensity)),
410
+ transitionProgress: 0
411
+ };
412
+ switch (type) {
413
+ case "fog":
414
+ this.currentEnv.fog = {
415
+ type: "exponential2",
416
+ color: "#c0c0c0",
417
+ density: 5e-3 * intensity
418
+ };
419
+ break;
420
+ case "rain":
421
+ case "storm":
422
+ this.currentEnv.fog = {
423
+ type: "exponential2",
424
+ color: "#808080",
425
+ density: 2e-3 * intensity
426
+ };
427
+ break;
428
+ case "snow":
429
+ this.currentEnv.fog = {
430
+ type: "exponential2",
431
+ color: "#e0e0e0",
432
+ density: 3e-3 * intensity
433
+ };
434
+ break;
435
+ default:
436
+ break;
437
+ }
438
+ }
439
+ getWeather() {
440
+ return { ...this.weather };
441
+ }
442
+ updateWeatherTransition(dt) {
443
+ if (this.weather.transitionProgress < 1) {
444
+ this.weather.transitionProgress = Math.min(1, this.weather.transitionProgress + dt * 0.5);
445
+ }
446
+ }
447
+ };
448
+
449
+ // src/environment/FoliageSystem.ts
450
+ var FoliageSystem = class {
451
+ types = /* @__PURE__ */ new Map();
452
+ patches = /* @__PURE__ */ new Map();
453
+ windDir = { x: 1, z: 0 };
454
+ windStrength = 0.5;
455
+ time = 0;
456
+ // ---------------------------------------------------------------------------
457
+ // Types
458
+ // ---------------------------------------------------------------------------
459
+ registerType(type) {
460
+ this.types.set(type.id, type);
461
+ }
462
+ getType(id) {
463
+ return this.types.get(id);
464
+ }
465
+ getTypeCount() {
466
+ return this.types.size;
467
+ }
468
+ // ---------------------------------------------------------------------------
469
+ // Wind
470
+ // ---------------------------------------------------------------------------
471
+ setWind(dirX, dirZ, strength) {
472
+ const len = Math.sqrt(dirX * dirX + dirZ * dirZ) || 1;
473
+ this.windDir = { x: dirX / len, z: dirZ / len };
474
+ this.windStrength = Math.max(0, Math.min(1, strength));
475
+ }
476
+ getWind() {
477
+ return { dirX: this.windDir.x, dirZ: this.windDir.z, strength: this.windStrength };
478
+ }
479
+ // ---------------------------------------------------------------------------
480
+ // Scattering
481
+ // ---------------------------------------------------------------------------
482
+ scatter(patchId, typeId, bounds, count, seed = 42) {
483
+ const type = this.types.get(typeId);
484
+ if (!type) throw new Error(`Unknown foliage type: ${typeId}`);
485
+ const instances = [];
486
+ let rng = seed;
487
+ const nextRand = () => {
488
+ rng = rng * 1103515245 + 12345 & 2147483647;
489
+ return rng / 2147483647;
490
+ };
491
+ for (let i = 0; i < count; i++) {
492
+ const x = bounds.x + nextRand() * bounds.w;
493
+ const z = bounds.z + nextRand() * bounds.h;
494
+ const scale = type.minScale + nextRand() * (type.maxScale - type.minScale);
495
+ instances.push({
496
+ typeId,
497
+ position: { x, y: 0, z },
498
+ scale,
499
+ rotation: nextRand() * Math.PI * 2,
500
+ windPhase: nextRand() * Math.PI * 2,
501
+ lodLevel: 0,
502
+ visible: true
503
+ });
504
+ }
505
+ const patch = {
506
+ id: patchId,
507
+ bounds,
508
+ instances,
509
+ density: count / (bounds.w * bounds.h)
510
+ };
511
+ this.patches.set(patchId, patch);
512
+ return patch;
513
+ }
514
+ // ---------------------------------------------------------------------------
515
+ // Update
516
+ // ---------------------------------------------------------------------------
517
+ update(dt, cameraPos) {
518
+ this.time += dt;
519
+ for (const patch of this.patches.values()) {
520
+ for (const inst of patch.instances) {
521
+ const type = this.types.get(inst.typeId);
522
+ if (!type) continue;
523
+ const dx = inst.position.x - cameraPos.x;
524
+ const dz = inst.position.z - cameraPos.z;
525
+ const dist = Math.sqrt(dx * dx + dz * dz);
526
+ inst.lodLevel = 0;
527
+ inst.visible = true;
528
+ for (let l = 0; l < type.lodDistances.length; l++) {
529
+ if (dist > type.lodDistances[l]) inst.lodLevel = l + 1;
530
+ }
531
+ if (inst.lodLevel > type.lodDistances.length) inst.visible = false;
532
+ }
533
+ }
534
+ }
535
+ // ---------------------------------------------------------------------------
536
+ // Wind sway (queried per instance for rendering)
537
+ // ---------------------------------------------------------------------------
538
+ getWindOffset(inst) {
539
+ const type = this.types.get(inst.typeId);
540
+ const response = type?.windResponse ?? 0;
541
+ const sway = Math.sin(this.time * 2 + inst.windPhase) * this.windStrength * response;
542
+ return { x: this.windDir.x * sway, z: this.windDir.z * sway };
543
+ }
544
+ // ---------------------------------------------------------------------------
545
+ // Queries
546
+ // ---------------------------------------------------------------------------
547
+ getPatch(id) {
548
+ return this.patches.get(id);
549
+ }
550
+ getPatchCount() {
551
+ return this.patches.size;
552
+ }
553
+ getVisibleCount() {
554
+ let count = 0;
555
+ for (const p of this.patches.values()) {
556
+ for (const i of p.instances) if (i.visible) count++;
557
+ }
558
+ return count;
559
+ }
560
+ getTotalInstanceCount() {
561
+ let count = 0;
562
+ for (const p of this.patches.values()) count += p.instances.length;
563
+ return count;
564
+ }
565
+ removePatch(id) {
566
+ return this.patches.delete(id);
567
+ }
568
+ };
569
+
570
+ // src/environment/GrassRenderer.ts
571
+ var GrassRenderer = class {
572
+ config;
573
+ blades = [];
574
+ constructor(config) {
575
+ this.config = {
576
+ baseHeight: 0.3,
577
+ heightVariation: 0.15,
578
+ baseWidth: 0.02,
579
+ widthVariation: 0.01,
580
+ baseColor: { r: 0.2, g: 0.6, b: 0.1 },
581
+ tipColor: { r: 0.5, g: 0.8, b: 0.2 },
582
+ colorVariation: 0.1,
583
+ billboardDistance: 30,
584
+ cullDistance: 60,
585
+ bendRange: 0.3,
586
+ bladesPerUnit: 10,
587
+ ...config
588
+ };
589
+ }
590
+ // ---------------------------------------------------------------------------
591
+ // Generation
592
+ // ---------------------------------------------------------------------------
593
+ generate(bounds, seed = 123) {
594
+ this.blades = [];
595
+ const area = bounds.w * bounds.h;
596
+ const count = Math.floor(area * this.config.bladesPerUnit);
597
+ let rng = seed;
598
+ const rand = () => {
599
+ rng = rng * 1103515245 + 12345 & 2147483647;
600
+ return rng / 2147483647;
601
+ };
602
+ for (let i = 0; i < count; i++) {
603
+ const x = bounds.x + rand() * bounds.w;
604
+ const z = bounds.z + rand() * bounds.h;
605
+ const heightMul = 1 - this.config.heightVariation + rand() * this.config.heightVariation * 2;
606
+ const widthMul = 1 - this.config.widthVariation / this.config.baseWidth + rand() * (this.config.widthVariation / this.config.baseWidth) * 2;
607
+ const colorVar = (rand() - 0.5) * 2 * this.config.colorVariation;
608
+ this.blades.push({
609
+ position: { x, y: 0, z },
610
+ height: this.config.baseHeight * heightMul,
611
+ width: this.config.baseWidth * Math.max(0.5, widthMul),
612
+ bendFactor: rand() * this.config.bendRange,
613
+ color: {
614
+ r: Math.max(0, Math.min(1, this.config.baseColor.r + colorVar)),
615
+ g: Math.max(0, Math.min(1, this.config.baseColor.g + colorVar)),
616
+ b: Math.max(0, Math.min(1, this.config.baseColor.b + colorVar))
617
+ },
618
+ lodLevel: 0,
619
+ isBillboard: false
620
+ });
621
+ }
622
+ }
623
+ // ---------------------------------------------------------------------------
624
+ // Update LOD
625
+ // ---------------------------------------------------------------------------
626
+ updateLOD(cameraPos) {
627
+ for (const blade of this.blades) {
628
+ const dx = blade.position.x - cameraPos.x;
629
+ const dz = blade.position.z - cameraPos.z;
630
+ const dist = Math.sqrt(dx * dx + dz * dz);
631
+ if (dist > this.config.cullDistance) {
632
+ blade.lodLevel = -1;
633
+ blade.isBillboard = false;
634
+ } else if (dist > this.config.billboardDistance) {
635
+ blade.lodLevel = 2;
636
+ blade.isBillboard = true;
637
+ } else if (dist > this.config.billboardDistance * 0.5) {
638
+ blade.lodLevel = 1;
639
+ blade.isBillboard = false;
640
+ } else {
641
+ blade.lodLevel = 0;
642
+ blade.isBillboard = false;
643
+ }
644
+ }
645
+ }
646
+ // ---------------------------------------------------------------------------
647
+ // Queries
648
+ // ---------------------------------------------------------------------------
649
+ getBladeCount() {
650
+ return this.blades.length;
651
+ }
652
+ getVisibleBlades() {
653
+ return this.blades.filter((b) => b.lodLevel >= 0);
654
+ }
655
+ getBillboardCount() {
656
+ return this.blades.filter((b) => b.isBillboard).length;
657
+ }
658
+ getConfig() {
659
+ return { ...this.config };
660
+ }
661
+ setConfig(config) {
662
+ Object.assign(this.config, config);
663
+ }
664
+ };
665
+
666
+ // src/environment/SkyRenderer.ts
667
+ var _cloudId = 0;
668
+ var SkyRenderer = class {
669
+ gradient = {
670
+ top: { r: 0.3, g: 0.5, b: 0.9 },
671
+ horizon: { r: 0.7, g: 0.8, b: 1 },
672
+ bottom: { r: 0.4, g: 0.4, b: 0.5 }
673
+ };
674
+ stars = { seed: 42, density: 100, brightness: 1, twinkleSpeed: 1 };
675
+ clouds = /* @__PURE__ */ new Map();
676
+ sun = {
677
+ visible: true,
678
+ angle: 90,
679
+ size: 10,
680
+ color: { r: 1, g: 0.95, b: 0.8 },
681
+ intensity: 1
682
+ };
683
+ moon = {
684
+ visible: false,
685
+ angle: -45,
686
+ size: 8,
687
+ color: { r: 0.9, g: 0.9, b: 1 },
688
+ intensity: 0.3
689
+ };
690
+ moonPhase = 0;
691
+ // 0-7 (new, waxing crescent, first quarter, etc.)
692
+ starsVisible = false;
693
+ // ---------------------------------------------------------------------------
694
+ // Gradient
695
+ // ---------------------------------------------------------------------------
696
+ setGradient(gradient) {
697
+ this.gradient = { ...gradient };
698
+ }
699
+ getGradient() {
700
+ return { ...this.gradient };
701
+ }
702
+ sampleGradient(t) {
703
+ if (t <= 0.5) {
704
+ const localT = t * 2;
705
+ return this.lerpColor(this.gradient.bottom, this.gradient.horizon, localT);
706
+ } else {
707
+ const localT = (t - 0.5) * 2;
708
+ return this.lerpColor(this.gradient.horizon, this.gradient.top, localT);
709
+ }
710
+ }
711
+ lerpColor(a, b, t) {
712
+ return { r: a.r + (b.r - a.r) * t, g: a.g + (b.g - a.g) * t, b: a.b + (b.b - a.b) * t };
713
+ }
714
+ // ---------------------------------------------------------------------------
715
+ // Stars
716
+ // ---------------------------------------------------------------------------
717
+ setStarField(config) {
718
+ Object.assign(this.stars, config);
719
+ }
720
+ setStarsVisible(visible) {
721
+ this.starsVisible = visible;
722
+ }
723
+ areStarsVisible() {
724
+ return this.starsVisible;
725
+ }
726
+ getStarField() {
727
+ return { ...this.stars };
728
+ }
729
+ // ---------------------------------------------------------------------------
730
+ // Clouds
731
+ // ---------------------------------------------------------------------------
732
+ addCloudLayer(coverage, altitude = 1e3, speed) {
733
+ const id = `cloud_${_cloudId++}`;
734
+ const layer = {
735
+ id,
736
+ altitude,
737
+ coverage,
738
+ speed: speed ?? { x: 5, z: 0 },
739
+ color: { r: 1, g: 1, b: 1, a: 0.8 },
740
+ scale: 1,
741
+ offset: { x: 0, z: 0 }
742
+ };
743
+ this.clouds.set(id, layer);
744
+ return layer;
745
+ }
746
+ removeCloudLayer(id) {
747
+ return this.clouds.delete(id);
748
+ }
749
+ getCloudLayers() {
750
+ return [...this.clouds.values()];
751
+ }
752
+ getCloudCount() {
753
+ return this.clouds.size;
754
+ }
755
+ updateClouds(dt) {
756
+ for (const layer of this.clouds.values()) {
757
+ layer.offset.x += layer.speed.x * dt;
758
+ layer.offset.z += layer.speed.z * dt;
759
+ }
760
+ }
761
+ // ---------------------------------------------------------------------------
762
+ // Sun / Moon
763
+ // ---------------------------------------------------------------------------
764
+ setSunAngle(angle) {
765
+ this.sun.angle = angle;
766
+ this.sun.visible = angle >= 0;
767
+ }
768
+ setMoonAngle(angle) {
769
+ this.moon.angle = angle;
770
+ this.moon.visible = angle >= 0;
771
+ }
772
+ setMoonPhase(phase) {
773
+ this.moonPhase = phase % 8;
774
+ }
775
+ getMoonPhase() {
776
+ return this.moonPhase;
777
+ }
778
+ getSun() {
779
+ return { ...this.sun };
780
+ }
781
+ getMoon() {
782
+ return { ...this.moon };
783
+ }
784
+ // ---------------------------------------------------------------------------
785
+ // Time-of-Day Presets
786
+ // ---------------------------------------------------------------------------
787
+ applyDaytime() {
788
+ this.setGradient({
789
+ top: { r: 0.25, g: 0.45, b: 0.85 },
790
+ horizon: { r: 0.6, g: 0.75, b: 0.95 },
791
+ bottom: { r: 0.4, g: 0.4, b: 0.5 }
792
+ });
793
+ this.starsVisible = false;
794
+ }
795
+ applySunset() {
796
+ this.setGradient({
797
+ top: { r: 0.3, g: 0.3, b: 0.6 },
798
+ horizon: { r: 1, g: 0.5, b: 0.2 },
799
+ bottom: { r: 0.5, g: 0.2, b: 0.1 }
800
+ });
801
+ this.starsVisible = false;
802
+ }
803
+ applyNight() {
804
+ this.setGradient({
805
+ top: { r: 0.02, g: 0.02, b: 0.08 },
806
+ horizon: { r: 0.05, g: 0.05, b: 0.15 },
807
+ bottom: { r: 0.03, g: 0.03, b: 0.05 }
808
+ });
809
+ this.starsVisible = true;
810
+ }
811
+ // ---------------------------------------------------------------------------
812
+ // Queries
813
+ // ---------------------------------------------------------------------------
814
+ getTotalCoverage() {
815
+ let maxCoverage = 0;
816
+ for (const layer of this.clouds.values()) {
817
+ maxCoverage = Math.max(maxCoverage, layer.coverage);
818
+ }
819
+ return maxCoverage;
820
+ }
821
+ };
822
+
823
+ // src/environment/TerrainBrush.ts
824
+ var DEFAULT_BRUSH = {
825
+ mode: "raise",
826
+ radius: 5,
827
+ strength: 0.1,
828
+ falloff: 0.7
829
+ };
830
+ var TerrainBrush = class {
831
+ config;
832
+ terrain;
833
+ undoStack = [];
834
+ redoStack = [];
835
+ constructor(terrain, config = {}) {
836
+ this.terrain = terrain;
837
+ this.config = { ...DEFAULT_BRUSH, ...config };
838
+ }
839
+ // ---------------------------------------------------------------------------
840
+ // Configuration
841
+ // ---------------------------------------------------------------------------
842
+ setConfig(config) {
843
+ this.config = { ...this.config, ...config };
844
+ }
845
+ getConfig() {
846
+ return { ...this.config };
847
+ }
848
+ setMode(mode) {
849
+ this.config.mode = mode;
850
+ }
851
+ setRadius(radius) {
852
+ this.config.radius = Math.max(1, Math.min(50, radius));
853
+ }
854
+ setStrength(strength) {
855
+ this.config.strength = Math.max(0, Math.min(1, strength));
856
+ }
857
+ // ---------------------------------------------------------------------------
858
+ // Apply Brush
859
+ // ---------------------------------------------------------------------------
860
+ /**
861
+ * Apply the brush at a grid position on a terrain.
862
+ */
863
+ apply(terrainId, gridX, gridZ) {
864
+ const terrainData = this.terrain.getTerrain(terrainId);
865
+ if (!terrainData) {
866
+ return { terrainId, gridX, gridZ, config: { ...this.config }, affectedCells: [] };
867
+ }
868
+ const res = terrainData.config.resolution;
869
+ const heightmap = terrainData.heightmap;
870
+ const affectedCells = [];
871
+ const r = this.config.radius;
872
+ for (let dz = -r; dz <= r; dz++) {
873
+ for (let dx = -r; dx <= r; dx++) {
874
+ const cx = gridX + dx;
875
+ const cz = gridZ + dz;
876
+ if (cx < 0 || cx >= res || cz < 0 || cz >= res) continue;
877
+ const dist = Math.sqrt(dx * dx + dz * dz);
878
+ if (dist > r) continue;
879
+ const t = dist / r;
880
+ const falloff = 1 - Math.pow(t, 1 / Math.max(this.config.falloff, 0.01));
881
+ const influence = falloff * this.config.strength;
882
+ const idx = cz * res + cx;
883
+ const oldHeight = heightmap[idx];
884
+ let newHeight = oldHeight;
885
+ switch (this.config.mode) {
886
+ case "raise":
887
+ newHeight = Math.min(1, oldHeight + influence * 0.05);
888
+ break;
889
+ case "lower":
890
+ newHeight = Math.max(0, oldHeight - influence * 0.05);
891
+ break;
892
+ case "flatten":
893
+ const target = this.config.flattenHeight ?? 0.5;
894
+ newHeight = oldHeight + (target - oldHeight) * influence;
895
+ break;
896
+ case "smooth": {
897
+ let sum = 0;
898
+ let count = 0;
899
+ for (let sz = -1; sz <= 1; sz++) {
900
+ for (let sx = -1; sx <= 1; sx++) {
901
+ const nx = cx + sx;
902
+ const nz = cz + sz;
903
+ if (nx >= 0 && nx < res && nz >= 0 && nz < res) {
904
+ sum += heightmap[nz * res + nx];
905
+ count++;
906
+ }
907
+ }
908
+ }
909
+ const avg = sum / count;
910
+ newHeight = oldHeight + (avg - oldHeight) * influence;
911
+ break;
912
+ }
913
+ case "paint":
914
+ break;
915
+ }
916
+ if (newHeight !== oldHeight) {
917
+ heightmap[idx] = newHeight;
918
+ affectedCells.push({ x: cx, z: cz, oldHeight, newHeight });
919
+ }
920
+ }
921
+ }
922
+ const stroke = {
923
+ terrainId,
924
+ gridX,
925
+ gridZ,
926
+ config: { ...this.config },
927
+ affectedCells
928
+ };
929
+ this.undoStack.push(stroke);
930
+ this.redoStack = [];
931
+ return stroke;
932
+ }
933
+ // ---------------------------------------------------------------------------
934
+ // Undo/Redo
935
+ // ---------------------------------------------------------------------------
936
+ undo() {
937
+ const stroke = this.undoStack.pop();
938
+ if (!stroke) return null;
939
+ const terrainData = this.terrain.getTerrain(stroke.terrainId);
940
+ if (!terrainData) return null;
941
+ const res = terrainData.config.resolution;
942
+ for (const cell of stroke.affectedCells) {
943
+ terrainData.heightmap[cell.z * res + cell.x] = cell.oldHeight;
944
+ }
945
+ this.redoStack.push(stroke);
946
+ return stroke;
947
+ }
948
+ redo() {
949
+ const stroke = this.redoStack.pop();
950
+ if (!stroke) return null;
951
+ const terrainData = this.terrain.getTerrain(stroke.terrainId);
952
+ if (!terrainData) return null;
953
+ const res = terrainData.config.resolution;
954
+ for (const cell of stroke.affectedCells) {
955
+ terrainData.heightmap[cell.z * res + cell.x] = cell.newHeight;
956
+ }
957
+ this.undoStack.push(stroke);
958
+ return stroke;
959
+ }
960
+ getUndoCount() {
961
+ return this.undoStack.length;
962
+ }
963
+ getRedoCount() {
964
+ return this.redoStack.length;
965
+ }
966
+ };
967
+
968
+ // src/environment/TerrainSystem.ts
969
+ function hash2D(x, y) {
970
+ let n = x * 73856093 ^ y * 19349663;
971
+ n = (n ^ n >> 13) * 1274126177;
972
+ return ((n ^ n >> 16) & 2147483647) / 2147483647;
973
+ }
974
+ function smoothstep(t) {
975
+ return t * t * (3 - 2 * t);
976
+ }
977
+ function valueNoise2D(x, y) {
978
+ const ix = Math.floor(x);
979
+ const iy = Math.floor(y);
980
+ const fx = x - ix;
981
+ const fy = y - iy;
982
+ const sx = smoothstep(fx);
983
+ const sy = smoothstep(fy);
984
+ const n00 = hash2D(ix, iy);
985
+ const n10 = hash2D(ix + 1, iy);
986
+ const n01 = hash2D(ix, iy + 1);
987
+ const n11 = hash2D(ix + 1, iy + 1);
988
+ const nx0 = n00 + (n10 - n00) * sx;
989
+ const nx1 = n01 + (n11 - n01) * sx;
990
+ return nx0 + (nx1 - nx0) * sy;
991
+ }
992
+ function fbm(x, y, octaves, lacunarity, gain) {
993
+ let value = 0;
994
+ let amplitude = 1;
995
+ let frequency = 1;
996
+ let maxValue = 0;
997
+ for (let i = 0; i < octaves; i++) {
998
+ value += valueNoise2D(x * frequency, y * frequency) * amplitude;
999
+ maxValue += amplitude;
1000
+ amplitude *= gain;
1001
+ frequency *= lacunarity;
1002
+ }
1003
+ return value / maxValue;
1004
+ }
1005
+ var TerrainSystem = class {
1006
+ terrains = /* @__PURE__ */ new Map();
1007
+ // ---------------------------------------------------------------------------
1008
+ // Creation
1009
+ // ---------------------------------------------------------------------------
1010
+ /**
1011
+ * Create a terrain with a procedurally generated heightmap.
1012
+ */
1013
+ createTerrain(config, options = {}) {
1014
+ const { octaves = 6, lacunarity = 2, gain = 0.5, seed = 42, scale = 0.02 } = options;
1015
+ const res = config.resolution;
1016
+ const heightmap = new Float32Array(res * res);
1017
+ for (let z = 0; z < res; z++) {
1018
+ for (let x = 0; x < res; x++) {
1019
+ const nx = (x + seed) * scale;
1020
+ const nz = (z + seed * 0.7) * scale;
1021
+ heightmap[z * res + x] = fbm(nx, nz, octaves, lacunarity, gain);
1022
+ }
1023
+ }
1024
+ const chunks = this.generateChunks(config, heightmap, 0);
1025
+ this.terrains.set(config.id, {
1026
+ config,
1027
+ heightmap,
1028
+ layers: this.getDefaultLayers(),
1029
+ chunks
1030
+ });
1031
+ return config.id;
1032
+ }
1033
+ /**
1034
+ * Create terrain from an existing heightmap array.
1035
+ */
1036
+ createFromHeightmap(config, heightmap) {
1037
+ const chunks = this.generateChunks(config, heightmap, 0);
1038
+ this.terrains.set(config.id, {
1039
+ config,
1040
+ heightmap,
1041
+ layers: this.getDefaultLayers(),
1042
+ chunks
1043
+ });
1044
+ return config.id;
1045
+ }
1046
+ // ---------------------------------------------------------------------------
1047
+ // Heightmap Access
1048
+ // ---------------------------------------------------------------------------
1049
+ /**
1050
+ * Get height at a world position (bilinear interpolation).
1051
+ */
1052
+ getHeightAt(terrainId, worldX, worldZ) {
1053
+ const terrain = this.terrains.get(terrainId);
1054
+ if (!terrain) return 0;
1055
+ const { config, heightmap } = terrain;
1056
+ const res = config.resolution;
1057
+ const localX = (worldX - config.position.x) / config.width;
1058
+ const localZ = (worldZ - config.position.z) / config.depth;
1059
+ if (localX < 0 || localX > 1 || localZ < 0 || localZ > 1) return 0;
1060
+ const gx = localX * (res - 1);
1061
+ const gz = localZ * (res - 1);
1062
+ const ix = Math.floor(gx);
1063
+ const iz = Math.floor(gz);
1064
+ const fx = gx - ix;
1065
+ const fz = gz - iz;
1066
+ const ix1 = Math.min(ix + 1, res - 1);
1067
+ const iz1 = Math.min(iz + 1, res - 1);
1068
+ const h00 = heightmap[iz * res + ix];
1069
+ const h10 = heightmap[iz * res + ix1];
1070
+ const h01 = heightmap[iz1 * res + ix];
1071
+ const h11 = heightmap[iz1 * res + ix1];
1072
+ const h = h00 * (1 - fx) * (1 - fz) + h10 * fx * (1 - fz) + h01 * (1 - fx) * fz + h11 * fx * fz;
1073
+ return config.position.y + h * config.maxHeight;
1074
+ }
1075
+ /**
1076
+ * Get surface normal at a world position (finite differences).
1077
+ */
1078
+ getNormalAt(terrainId, worldX, worldZ) {
1079
+ const epsilon = 0.5;
1080
+ const hL = this.getHeightAt(terrainId, worldX - epsilon, worldZ);
1081
+ const hR = this.getHeightAt(terrainId, worldX + epsilon, worldZ);
1082
+ const hD = this.getHeightAt(terrainId, worldX, worldZ - epsilon);
1083
+ const hU = this.getHeightAt(terrainId, worldX, worldZ + epsilon);
1084
+ const nx = hL - hR;
1085
+ const nz = hD - hU;
1086
+ const ny = 2 * epsilon;
1087
+ const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
1088
+ return { x: nx / len, y: ny / len, z: nz / len };
1089
+ }
1090
+ /**
1091
+ * Set height at a specific grid position.
1092
+ */
1093
+ setHeightAt(terrainId, gridX, gridZ, height) {
1094
+ const terrain = this.terrains.get(terrainId);
1095
+ if (!terrain) return;
1096
+ const res = terrain.config.resolution;
1097
+ if (gridX < 0 || gridX >= res || gridZ < 0 || gridZ >= res) return;
1098
+ terrain.heightmap[gridZ * res + gridX] = Math.max(0, Math.min(1, height));
1099
+ }
1100
+ /**
1101
+ * Get the collider interface for a terrain.
1102
+ */
1103
+ getCollider(terrainId) {
1104
+ if (!this.terrains.has(terrainId)) return null;
1105
+ return {
1106
+ getHeightAt: (x, z) => this.getHeightAt(terrainId, x, z),
1107
+ getNormalAt: (x, z) => this.getNormalAt(terrainId, x, z)
1108
+ };
1109
+ }
1110
+ // ---------------------------------------------------------------------------
1111
+ // Layers
1112
+ // ---------------------------------------------------------------------------
1113
+ setLayers(terrainId, layers) {
1114
+ const terrain = this.terrains.get(terrainId);
1115
+ if (terrain) terrain.layers = layers;
1116
+ }
1117
+ getLayers(terrainId) {
1118
+ return this.terrains.get(terrainId)?.layers || [];
1119
+ }
1120
+ // ---------------------------------------------------------------------------
1121
+ // Queries
1122
+ // ---------------------------------------------------------------------------
1123
+ getTerrain(id) {
1124
+ return this.terrains.get(id);
1125
+ }
1126
+ getTerrainIds() {
1127
+ return [...this.terrains.keys()];
1128
+ }
1129
+ removeTerrain(id) {
1130
+ return this.terrains.delete(id);
1131
+ }
1132
+ getChunks(terrainId) {
1133
+ return this.terrains.get(terrainId)?.chunks || [];
1134
+ }
1135
+ // ---------------------------------------------------------------------------
1136
+ // Internal
1137
+ // ---------------------------------------------------------------------------
1138
+ generateChunks(config, heightmap, lod) {
1139
+ const res = config.resolution;
1140
+ const chunkSize = 32;
1141
+ const step = 1 << lod;
1142
+ const chunks = [];
1143
+ for (let cz = 0; cz < res - 1; cz += chunkSize) {
1144
+ for (let cx = 0; cx < res - 1; cx += chunkSize) {
1145
+ const vertices = [];
1146
+ const indices = [];
1147
+ const _vertexIndex = 0;
1148
+ const endX = Math.min(cx + chunkSize, res - 1);
1149
+ const endZ = Math.min(cz + chunkSize, res - 1);
1150
+ for (let z = cz; z <= endZ; z += step) {
1151
+ for (let x = cx; x <= endX; x += step) {
1152
+ const h = heightmap[z * res + x];
1153
+ const worldX = config.position.x + x / (res - 1) * config.width;
1154
+ const worldZ = config.position.z + z / (res - 1) * config.depth;
1155
+ const worldY = config.position.y + h * config.maxHeight;
1156
+ vertices.push({
1157
+ position: { x: worldX, y: worldY, z: worldZ },
1158
+ normal: { x: 0, y: 1, z: 0 },
1159
+ // Simplified; real normals computed post-pass
1160
+ uv: { u: x / (res - 1), v: z / (res - 1) },
1161
+ height: h
1162
+ });
1163
+ }
1164
+ }
1165
+ const rowVerts = Math.ceil((endX - cx) / step) + 1;
1166
+ const colVerts = Math.ceil((endZ - cz) / step) + 1;
1167
+ for (let row = 0; row < colVerts - 1; row++) {
1168
+ for (let col = 0; col < rowVerts - 1; col++) {
1169
+ const tl = row * rowVerts + col;
1170
+ const tr = tl + 1;
1171
+ const bl = (row + 1) * rowVerts + col;
1172
+ const br = bl + 1;
1173
+ indices.push(tl, bl, tr);
1174
+ indices.push(tr, bl, br);
1175
+ }
1176
+ }
1177
+ chunks.push({
1178
+ x: cx,
1179
+ z: cz,
1180
+ lod,
1181
+ vertices,
1182
+ indices
1183
+ });
1184
+ }
1185
+ }
1186
+ return chunks;
1187
+ }
1188
+ getDefaultLayers() {
1189
+ return [
1190
+ {
1191
+ id: "sand",
1192
+ texture: "terrain_sand",
1193
+ tiling: 20,
1194
+ minHeight: 0,
1195
+ maxHeight: 0.15,
1196
+ minSlope: 0,
1197
+ maxSlope: 0.3
1198
+ },
1199
+ {
1200
+ id: "grass",
1201
+ texture: "terrain_grass",
1202
+ tiling: 15,
1203
+ minHeight: 0.1,
1204
+ maxHeight: 0.6,
1205
+ minSlope: 0,
1206
+ maxSlope: 0.6
1207
+ },
1208
+ {
1209
+ id: "rock",
1210
+ texture: "terrain_rock",
1211
+ tiling: 10,
1212
+ minHeight: 0.4,
1213
+ maxHeight: 0.9,
1214
+ minSlope: 0.5,
1215
+ maxSlope: 1
1216
+ },
1217
+ {
1218
+ id: "snow",
1219
+ texture: "terrain_snow",
1220
+ tiling: 12,
1221
+ minHeight: 0.8,
1222
+ maxHeight: 1,
1223
+ minSlope: 0,
1224
+ maxSlope: 0.4
1225
+ }
1226
+ ];
1227
+ }
1228
+ };
1229
+
1230
+ // src/environment/TreePlacer.ts
1231
+ var _treeId = 0;
1232
+ var TreePlacer = class {
1233
+ templates = /* @__PURE__ */ new Map();
1234
+ biomes = /* @__PURE__ */ new Map();
1235
+ trees = [];
1236
+ // ---------------------------------------------------------------------------
1237
+ // Templates & Biomes
1238
+ // ---------------------------------------------------------------------------
1239
+ addTemplate(template) {
1240
+ this.templates.set(template.id, template);
1241
+ }
1242
+ getTemplate(id) {
1243
+ return this.templates.get(id);
1244
+ }
1245
+ getTemplateCount() {
1246
+ return this.templates.size;
1247
+ }
1248
+ addBiome(biome) {
1249
+ this.biomes.set(biome.id, biome);
1250
+ }
1251
+ getBiome(id) {
1252
+ return this.biomes.get(id);
1253
+ }
1254
+ // ---------------------------------------------------------------------------
1255
+ // Placement
1256
+ // ---------------------------------------------------------------------------
1257
+ placeInRegion(biomeId, bounds, heightSampler, slopeSampler, seed = 7) {
1258
+ const biome = this.biomes.get(biomeId);
1259
+ if (!biome) return [];
1260
+ const validTemplates = [...this.templates.values()].filter((t) => t.biomes.includes(biomeId));
1261
+ if (validTemplates.length === 0) return [];
1262
+ const totalWeight = validTemplates.reduce((sum, t) => sum + t.probability, 0);
1263
+ const area = bounds.w * bounds.h;
1264
+ const count = Math.floor(area * biome.density);
1265
+ let rng = seed;
1266
+ const rand = () => {
1267
+ rng = rng * 1103515245 + 12345 & 2147483647;
1268
+ return rng / 2147483647;
1269
+ };
1270
+ const placed = [];
1271
+ for (let attempt = 0; attempt < count * 3 && placed.length < count; attempt++) {
1272
+ const x = bounds.x + rand() * bounds.w;
1273
+ const z = bounds.z + rand() * bounds.h;
1274
+ const y = heightSampler ? heightSampler(x, z) : 0;
1275
+ if (y < biome.heightRange.min || y > biome.heightRange.max) continue;
1276
+ if (slopeSampler) {
1277
+ const slope = slopeSampler(x, z);
1278
+ if (slope > biome.slopeMax) continue;
1279
+ }
1280
+ if (!this.checkSpacing(x, z, biome.minSpacing, placed)) continue;
1281
+ const template = this.pickTemplate(validTemplates, totalWeight, rand);
1282
+ if (!template) continue;
1283
+ const scale = template.minScale + rand() * (template.maxScale - template.minScale);
1284
+ const tree = {
1285
+ id: `tree_${_treeId++}`,
1286
+ templateId: template.id,
1287
+ position: { x, y, z },
1288
+ scale,
1289
+ rotation: rand() * Math.PI * 2
1290
+ };
1291
+ placed.push(tree);
1292
+ this.trees.push(tree);
1293
+ }
1294
+ return placed;
1295
+ }
1296
+ checkSpacing(x, z, minSpacing, placed) {
1297
+ const minSq = minSpacing * minSpacing;
1298
+ for (const tree of placed) {
1299
+ const dx = tree.position.x - x;
1300
+ const dz = tree.position.z - z;
1301
+ if (dx * dx + dz * dz < minSq) return false;
1302
+ }
1303
+ for (const tree of this.trees) {
1304
+ const dx = tree.position.x - x;
1305
+ const dz = tree.position.z - z;
1306
+ if (dx * dx + dz * dz < minSq) return false;
1307
+ }
1308
+ return true;
1309
+ }
1310
+ pickTemplate(templates, totalWeight, rand) {
1311
+ let r = rand() * totalWeight;
1312
+ for (const t of templates) {
1313
+ r -= t.probability;
1314
+ if (r <= 0) return t;
1315
+ }
1316
+ return templates[templates.length - 1];
1317
+ }
1318
+ // ---------------------------------------------------------------------------
1319
+ // Queries
1320
+ // ---------------------------------------------------------------------------
1321
+ getPlacedCount() {
1322
+ return this.trees.length;
1323
+ }
1324
+ getAllTrees() {
1325
+ return [...this.trees];
1326
+ }
1327
+ getTreesInRadius(x, z, radius) {
1328
+ const rSq = radius * radius;
1329
+ return this.trees.filter((t) => {
1330
+ const dx = t.position.x - x;
1331
+ const dz = t.position.z - z;
1332
+ return dx * dx + dz * dz <= rSq;
1333
+ });
1334
+ }
1335
+ removeTree(id) {
1336
+ const idx = this.trees.findIndex((t) => t.id === id);
1337
+ if (idx === -1) return false;
1338
+ this.trees.splice(idx, 1);
1339
+ return true;
1340
+ }
1341
+ clear() {
1342
+ this.trees = [];
1343
+ }
1344
+ };
1345
+
1346
+ // src/environment/WeatherBlackboard.ts
1347
+ var DEFAULT_STATE = {
1348
+ wind_vector: [0, 0, 0],
1349
+ precipitation: 0,
1350
+ precipitation_type: "none",
1351
+ temperature: 20,
1352
+ humidity: 0.5,
1353
+ sun_position: [0.5, 0.866, 0],
1354
+ // ~60 degree elevation, noon
1355
+ sun_intensity: 1,
1356
+ cloud_density: 0.3,
1357
+ cloud_altitude: 2e3,
1358
+ fog_density: 0,
1359
+ time_of_day: 12,
1360
+ is_night: false,
1361
+ surface_wetness: 0,
1362
+ wind_speed: 0,
1363
+ visibility_range: 1e4,
1364
+ frame: 0
1365
+ };
1366
+ var weatherBlackboard = { ...DEFAULT_STATE };
1367
+ function updateWeatherBlackboard(partial) {
1368
+ Object.assign(weatherBlackboard, partial);
1369
+ const wv = weatherBlackboard.wind_vector;
1370
+ weatherBlackboard.wind_speed = Math.sqrt(wv[0] * wv[0] + wv[1] * wv[1] + wv[2] * wv[2]);
1371
+ weatherBlackboard.is_night = weatherBlackboard.sun_intensity < 0.1;
1372
+ weatherBlackboard.visibility_range = 1e4 * (1 - weatherBlackboard.fog_density * 0.9) * (1 - weatherBlackboard.precipitation * 0.5);
1373
+ if (weatherBlackboard.precipitation > 0 && weatherBlackboard.precipitation_type === "rain") {
1374
+ weatherBlackboard.surface_wetness = Math.min(
1375
+ 1,
1376
+ weatherBlackboard.surface_wetness + weatherBlackboard.precipitation * 0.01
1377
+ );
1378
+ } else {
1379
+ weatherBlackboard.surface_wetness = Math.max(0, weatherBlackboard.surface_wetness - 1e-3);
1380
+ }
1381
+ weatherBlackboard.frame++;
1382
+ }
1383
+ function resetWeatherBlackboard() {
1384
+ Object.assign(weatherBlackboard, DEFAULT_STATE);
1385
+ weatherBlackboard.frame = 0;
1386
+ }
1387
+ function computeSunPosition(timeOfDay, latitude = 45) {
1388
+ const hourAngle = (timeOfDay - 12) * (Math.PI / 12);
1389
+ const latRad = latitude * (Math.PI / 180);
1390
+ const maxElevation = Math.PI / 2 - Math.abs(latRad - 23.5 * Math.PI / 180);
1391
+ const elevation = Math.sin(Math.PI * timeOfDay / 24) * maxElevation;
1392
+ const x = Math.cos(hourAngle) * Math.cos(elevation);
1393
+ const y = Math.sin(elevation);
1394
+ const z = Math.sin(hourAngle) * Math.cos(elevation);
1395
+ const len = Math.sqrt(x * x + y * y + z * z);
1396
+ if (len < 1e-3) return [0, -1, 0];
1397
+ return [x / len, y / len, z / len];
1398
+ }
1399
+ function computeSunIntensity(sunY) {
1400
+ if (sunY <= 0) return 0;
1401
+ return Math.min(1, sunY * 2) * Math.min(1, sunY * 5);
1402
+ }
1403
+
1404
+ // src/environment/WeatherSystem.ts
1405
+ var WEATHER_DEFAULTS = {
1406
+ clear: { intensity: 0, visibility: 1, precipitation: 0, humidity: 0.3 },
1407
+ cloudy: { intensity: 0.3, visibility: 0.9, precipitation: 0, humidity: 0.5 },
1408
+ rain: { intensity: 0.6, visibility: 0.6, precipitation: 0.7, humidity: 0.8 },
1409
+ storm: { intensity: 1, visibility: 0.3, precipitation: 1, humidity: 0.95 },
1410
+ snow: { intensity: 0.5, visibility: 0.5, precipitation: 0.6, humidity: 0.7 },
1411
+ fog: { intensity: 0.4, visibility: 0.2, precipitation: 0, humidity: 0.9 },
1412
+ sandstorm: { intensity: 0.8, visibility: 0.15, precipitation: 0, humidity: 0.1 }
1413
+ };
1414
+ var WeatherSystem = class {
1415
+ current;
1416
+ transition = null;
1417
+ targetState = null;
1418
+ listeners = [];
1419
+ history = [];
1420
+ constructor(initial = "clear") {
1421
+ this.current = this.createState(initial);
1422
+ this.history.push({ type: initial, timestamp: Date.now() });
1423
+ }
1424
+ createState(type) {
1425
+ const defaults = WEATHER_DEFAULTS[type];
1426
+ return {
1427
+ type,
1428
+ intensity: defaults.intensity ?? 0,
1429
+ wind: { x: 0, y: 0, z: 0, speed: 0 },
1430
+ temperature: 20,
1431
+ humidity: defaults.humidity ?? 0.5,
1432
+ visibility: defaults.visibility ?? 1,
1433
+ precipitation: defaults.precipitation ?? 0
1434
+ };
1435
+ }
1436
+ // ---------------------------------------------------------------------------
1437
+ // Weather Control
1438
+ // ---------------------------------------------------------------------------
1439
+ setWeather(type, transitionDuration = 5) {
1440
+ if (type === this.current.type && !this.transition) return;
1441
+ this.targetState = this.createState(type);
1442
+ this.transition = {
1443
+ from: this.current.type,
1444
+ to: type,
1445
+ duration: transitionDuration,
1446
+ elapsed: 0
1447
+ };
1448
+ this.history.push({ type, timestamp: Date.now() });
1449
+ }
1450
+ setImmediate(type) {
1451
+ this.current = this.createState(type);
1452
+ this.transition = null;
1453
+ this.targetState = null;
1454
+ this.notify();
1455
+ }
1456
+ setWind(x, y, z, speed) {
1457
+ this.current.wind = { x, y, z, speed };
1458
+ }
1459
+ setTemperature(temp) {
1460
+ this.current.temperature = temp;
1461
+ }
1462
+ // ---------------------------------------------------------------------------
1463
+ // Update
1464
+ // ---------------------------------------------------------------------------
1465
+ update(dt) {
1466
+ if (!this.transition || !this.targetState) return;
1467
+ this.transition.elapsed += dt;
1468
+ const t = Math.min(1, this.transition.elapsed / this.transition.duration);
1469
+ const ease = t * t * (3 - 2 * t);
1470
+ this.current.intensity = this.lerp(this.current.intensity, this.targetState.intensity, ease);
1471
+ this.current.visibility = this.lerp(this.current.visibility, this.targetState.visibility, ease);
1472
+ this.current.precipitation = this.lerp(
1473
+ this.current.precipitation,
1474
+ this.targetState.precipitation,
1475
+ ease
1476
+ );
1477
+ this.current.humidity = this.lerp(this.current.humidity, this.targetState.humidity, ease);
1478
+ if (t >= 1) {
1479
+ this.current.type = this.transition.to;
1480
+ this.current = {
1481
+ ...this.targetState,
1482
+ wind: this.current.wind,
1483
+ temperature: this.current.temperature
1484
+ };
1485
+ this.transition = null;
1486
+ this.targetState = null;
1487
+ this.notify();
1488
+ }
1489
+ }
1490
+ lerp(a, b, t) {
1491
+ return a + (b - a) * t;
1492
+ }
1493
+ // ---------------------------------------------------------------------------
1494
+ // Events
1495
+ // ---------------------------------------------------------------------------
1496
+ onChange(listener) {
1497
+ this.listeners.push(listener);
1498
+ }
1499
+ notify() {
1500
+ for (const l of this.listeners) l(this.getState());
1501
+ }
1502
+ // ---------------------------------------------------------------------------
1503
+ // Queries
1504
+ // ---------------------------------------------------------------------------
1505
+ getState() {
1506
+ return { ...this.current, wind: { ...this.current.wind } };
1507
+ }
1508
+ getType() {
1509
+ return this.current.type;
1510
+ }
1511
+ isTransitioning() {
1512
+ return this.transition !== null;
1513
+ }
1514
+ getTransitionProgress() {
1515
+ return this.transition ? this.transition.elapsed / this.transition.duration : 0;
1516
+ }
1517
+ getHistory() {
1518
+ return [...this.history];
1519
+ }
1520
+ };
1521
+
1522
+ export {
1523
+ DayNightCycle,
1524
+ PRESET_SUNNY_DAY,
1525
+ PRESET_SUNSET,
1526
+ PRESET_NIGHT,
1527
+ PRESET_OVERCAST,
1528
+ PRESET_SCIFI,
1529
+ ALL_PRESETS,
1530
+ EnvironmentManager,
1531
+ FoliageSystem,
1532
+ GrassRenderer,
1533
+ SkyRenderer,
1534
+ TerrainBrush,
1535
+ TerrainSystem,
1536
+ TreePlacer,
1537
+ weatherBlackboard,
1538
+ updateWeatherBlackboard,
1539
+ resetWeatherBlackboard,
1540
+ computeSunPosition,
1541
+ computeSunIntensity,
1542
+ WeatherSystem,
1543
+ environment_exports
1544
+ };