aether-engine 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +15 -0
  2. package/biome.json +51 -0
  3. package/bun.lock +192 -0
  4. package/index.ts +1 -0
  5. package/package.json +25 -0
  6. package/serve.ts +125 -0
  7. package/src/audio/AudioEngine.ts +61 -0
  8. package/src/components/Animator3D.ts +65 -0
  9. package/src/components/AudioSource.ts +26 -0
  10. package/src/components/BitmapText.ts +25 -0
  11. package/src/components/Camera.ts +33 -0
  12. package/src/components/CameraFollow.ts +5 -0
  13. package/src/components/Collider.ts +16 -0
  14. package/src/components/Components.test.ts +68 -0
  15. package/src/components/Light.ts +15 -0
  16. package/src/components/MeshRenderer.ts +58 -0
  17. package/src/components/ParticleEmitter.ts +59 -0
  18. package/src/components/RigidBody.ts +9 -0
  19. package/src/components/ShadowCaster.ts +3 -0
  20. package/src/components/SkinnedMeshRenderer.ts +25 -0
  21. package/src/components/SpriteAnimator.ts +42 -0
  22. package/src/components/SpriteRenderer.ts +26 -0
  23. package/src/components/Transform.test.ts +39 -0
  24. package/src/components/Transform.ts +54 -0
  25. package/src/core/AssetManager.ts +123 -0
  26. package/src/core/Input.test.ts +67 -0
  27. package/src/core/Input.ts +94 -0
  28. package/src/core/Scene.ts +24 -0
  29. package/src/core/SceneManager.ts +57 -0
  30. package/src/core/Storage.ts +161 -0
  31. package/src/desktop/SteamClient.ts +52 -0
  32. package/src/ecs/System.ts +11 -0
  33. package/src/ecs/World.test.ts +29 -0
  34. package/src/ecs/World.ts +149 -0
  35. package/src/index.ts +115 -0
  36. package/src/math/Color.ts +100 -0
  37. package/src/math/Vector2.ts +96 -0
  38. package/src/math/Vector3.ts +103 -0
  39. package/src/math/math.test.ts +168 -0
  40. package/src/renderer/GlowMaterial.ts +66 -0
  41. package/src/renderer/LitMaterial.ts +337 -0
  42. package/src/renderer/Material.test.ts +23 -0
  43. package/src/renderer/Material.ts +80 -0
  44. package/src/renderer/OcclusionMaterial.ts +43 -0
  45. package/src/renderer/ParticleMaterial.ts +66 -0
  46. package/src/renderer/Shader.ts +44 -0
  47. package/src/renderer/SkinnedLitMaterial.ts +55 -0
  48. package/src/renderer/WaterMaterial.ts +298 -0
  49. package/src/renderer/WebGLRenderer.ts +917 -0
  50. package/src/systems/Animation3DSystem.ts +148 -0
  51. package/src/systems/AnimationSystem.ts +58 -0
  52. package/src/systems/AudioSystem.ts +62 -0
  53. package/src/systems/LightingSystem.ts +114 -0
  54. package/src/systems/ParticleSystem.ts +278 -0
  55. package/src/systems/PhysicsSystem.ts +211 -0
  56. package/src/systems/Systems.test.ts +165 -0
  57. package/src/systems/TextSystem.ts +153 -0
  58. package/src/ui/AnimationEditor.tsx +639 -0
  59. package/src/ui/BottomPanel.tsx +443 -0
  60. package/src/ui/EntityExplorer.tsx +420 -0
  61. package/src/ui/GameState.ts +286 -0
  62. package/src/ui/Icons.tsx +239 -0
  63. package/src/ui/InventoryPanel.tsx +335 -0
  64. package/src/ui/PlayerHUD.tsx +250 -0
  65. package/src/ui/SpriteEditor.tsx +3241 -0
  66. package/src/ui/SpriteSheetManager.tsx +198 -0
  67. package/src/utils/GLTFLoader.ts +257 -0
  68. package/src/utils/ObjLoader.ts +81 -0
  69. package/src/utils/idb.ts +137 -0
  70. package/src/utils/packer.ts +85 -0
  71. package/test_obj.ts +12 -0
  72. package/tsconfig.json +21 -0
  73. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,443 @@
1
+ import { createSignal, onCleanup, onMount, Show } from "solid-js";
2
+ import { type AetherEngine, SpriteRenderer, Transform } from "../index";
3
+ import { AnimationEditor } from "./AnimationEditor";
4
+ import { EntityExplorer } from "./EntityExplorer";
5
+ import { IconAetherEngine, IconChevronDown, IconChevronUp } from "./Icons";
6
+ import { SpriteEditor } from "./SpriteEditor";
7
+ import { SpriteSheetManager } from "./SpriteSheetManager";
8
+
9
+ type Tab = "scene" | "sprite" | "animation" | "atlas";
10
+
11
+ export function BottomPanel(props: { engine: AetherEngine }) {
12
+ const [activeTab, setActiveTab] = createSignal<Tab>("scene");
13
+ const [fps, setFps] = createSignal(0);
14
+ const [entities, setEntities] = createSignal(0);
15
+
16
+ const [height, setHeight] = createSignal(350);
17
+ const [collapsed, setCollapsed] = createSignal(true);
18
+ const [isDragging, setIsDragging] = createSignal(false);
19
+
20
+ onMount(() => {
21
+ const handleKeyDown = (e: KeyboardEvent) => {
22
+ // Standard VS Code / Web IDE mapping for bottom panel toggles.
23
+ // Note: macOS intercepts Cmd + ` natively for Window Switching, so we supply Cmd+J (VS Code style) natively!
24
+ if (
25
+ (e.code === "KeyJ" || e.code === "Backquote") &&
26
+ (e.ctrlKey || e.metaKey)
27
+ ) {
28
+ e.preventDefault();
29
+ setCollapsed((c) => !c);
30
+ }
31
+ };
32
+ window.addEventListener("keydown", handleKeyDown);
33
+ onCleanup(() => window.removeEventListener("keydown", handleKeyDown));
34
+
35
+ let frames = 0;
36
+ let lastTime = performance.now();
37
+ const loop = () => {
38
+ frames++;
39
+ const now = performance.now();
40
+ if (now - lastTime >= 1000) {
41
+ setFps(frames);
42
+ frames = 0;
43
+ lastTime = now;
44
+ }
45
+ setEntities(props.engine.world.query(Transform).length);
46
+ requestAnimationFrame(loop);
47
+ };
48
+ requestAnimationFrame(loop);
49
+ });
50
+
51
+ const addSprite = () => {
52
+ const e = props.engine.world.createEntity();
53
+ props.engine.world.addComponent(e, Transform);
54
+ const t = props.engine.world.getComponent(e, Transform)!;
55
+ t.position[0] = Math.random() * 4 - 2;
56
+ t.position[1] = Math.random() * 4 - 2;
57
+ t.position[2] = -1;
58
+
59
+ props.engine.world.addComponent(
60
+ e,
61
+ SpriteRenderer,
62
+ "https://picsum.photos/400",
63
+ );
64
+ };
65
+
66
+ const startResize = (e: PointerEvent) => {
67
+ if (collapsed()) return;
68
+ setIsDragging(true);
69
+ e.preventDefault();
70
+ const handleMove = (eMove: PointerEvent) => {
71
+ const newHeight = window.innerHeight - eMove.clientY;
72
+ if (newHeight >= 100 && newHeight <= window.innerHeight - 50) {
73
+ setHeight(newHeight);
74
+ }
75
+ };
76
+ const handleUp = () => {
77
+ setIsDragging(false);
78
+ window.removeEventListener("pointermove", handleMove);
79
+ window.removeEventListener("pointerup", handleUp);
80
+ };
81
+ window.addEventListener("pointermove", handleMove);
82
+ window.addEventListener("pointerup", handleUp);
83
+ };
84
+
85
+ const themeDark = "#383838";
86
+ const themeHeader = "#2D2D2D";
87
+ const themeBorder = "#222222";
88
+ const themeActiveTab = "#3C3C3C";
89
+ const themeText = "#B4B4B4";
90
+ const themeActiveText = "#EEEEEE";
91
+
92
+ return (
93
+ <div
94
+ style={{
95
+ position: "fixed",
96
+ bottom: 0,
97
+ left: 0,
98
+ right: 0,
99
+ height: collapsed() ? "auto" : `${height()}px`,
100
+ background: themeDark,
101
+ "border-top": `1px solid ${themeBorder}`,
102
+ color: themeActiveText,
103
+ "font-family": "Arial, Helvetica, sans-serif",
104
+ "z-index": 9999,
105
+ display: "flex",
106
+ "flex-direction": "column",
107
+ "box-shadow": "0 -5px 15px rgba(0,0,0,0.3)",
108
+ "user-select": "none",
109
+ "-webkit-user-select": "none",
110
+ }}
111
+ >
112
+ {/* Resizer Handle */}
113
+ <div
114
+ onPointerDown={startResize}
115
+ style={{
116
+ position: "absolute",
117
+ top: "-3px",
118
+ left: 0,
119
+ right: 0,
120
+ height: "6px",
121
+ cursor: collapsed() ? "default" : "row-resize",
122
+ "z-index": 10000,
123
+ background: isDragging() ? "#3A72B0" : "transparent",
124
+ }}
125
+ />
126
+
127
+ {/* Tabs Header */}
128
+ <div
129
+ style={{
130
+ display: "flex",
131
+ background: themeHeader,
132
+ "border-bottom": `1px solid ${themeBorder}`,
133
+ height: "28px",
134
+ "align-items": "flex-end",
135
+ "padding-left": "5px",
136
+ }}
137
+ >
138
+ <button
139
+ title="Toggle DevTools Panel (Cmd/Ctrl + J or Ctrl + `)"
140
+ onClick={() => setCollapsed(!collapsed())}
141
+ style={{
142
+ padding: "3px 5px",
143
+ margin: "0 3px 2px 0",
144
+ background: "transparent",
145
+ color: themeText,
146
+ border: "none",
147
+ cursor: "pointer",
148
+ display: "flex",
149
+ "align-items": "center",
150
+ "justify-content": "center",
151
+ "border-radius": "3px",
152
+ }}
153
+ onMouseOver={(e) => (e.currentTarget.style.background = "#444")}
154
+ onMouseOut={(e) => (e.currentTarget.style.background = "transparent")}
155
+ >
156
+ <div
157
+ style={{
158
+ "font-weight": "bold",
159
+ "font-size": "14px",
160
+ "line-height": "14px",
161
+ }}
162
+ >
163
+ {collapsed() ? (
164
+ <IconChevronUp size={14} />
165
+ ) : (
166
+ <IconChevronDown size={14} />
167
+ )}
168
+ </div>
169
+ </button>
170
+
171
+ <div
172
+ style={{
173
+ "margin-right": "15px",
174
+ "margin-left": "5px",
175
+ "margin-top": "2px",
176
+ color: themeActiveText,
177
+ "font-size": "13px",
178
+ display: "flex",
179
+ "align-items": "center",
180
+ "user-select": "none",
181
+ height: "100%",
182
+ }}
183
+ >
184
+ <span
185
+ style={{
186
+ "font-weight": "bold",
187
+ "margin-right": "5px",
188
+ "min-width": "18px",
189
+ "text-align": "right",
190
+ }}
191
+ >
192
+ {fps()}
193
+ </span>{" "}
194
+ <span style={{ color: themeText }}>FPS</span>
195
+ </div>
196
+
197
+ <button
198
+ onClick={() => {
199
+ setActiveTab("scene");
200
+ setCollapsed(false);
201
+ }}
202
+ style={{
203
+ padding: "6px 15px",
204
+ margin: "0 1px",
205
+ background:
206
+ activeTab() === "scene" && !collapsed()
207
+ ? themeActiveTab
208
+ : "transparent",
209
+ color:
210
+ activeTab() === "scene" && !collapsed()
211
+ ? themeActiveText
212
+ : themeText,
213
+ border: "none",
214
+ cursor: "pointer",
215
+ "font-size": "12px",
216
+ "border-top":
217
+ activeTab() === "scene" && !collapsed()
218
+ ? "2px solid #3A72B0"
219
+ : "2px solid transparent",
220
+ "border-radius": "0",
221
+ }}
222
+ >
223
+ Scene
224
+ </button>
225
+ <button
226
+ onClick={() => {
227
+ setActiveTab("sprite");
228
+ setCollapsed(false);
229
+ }}
230
+ style={{
231
+ padding: "6px 15px",
232
+ margin: "0 1px",
233
+ background:
234
+ activeTab() === "sprite" && !collapsed()
235
+ ? themeActiveTab
236
+ : "transparent",
237
+ color:
238
+ activeTab() === "sprite" && !collapsed()
239
+ ? themeActiveText
240
+ : themeText,
241
+ border: "none",
242
+ cursor: "pointer",
243
+ "font-size": "12px",
244
+ "border-top":
245
+ activeTab() === "sprite" && !collapsed()
246
+ ? "2px solid #3A72B0"
247
+ : "2px solid transparent",
248
+ "border-radius": "0",
249
+ }}
250
+ >
251
+ Sprite Editor
252
+ </button>
253
+ <button
254
+ onClick={() => {
255
+ setActiveTab("animation");
256
+ setCollapsed(false);
257
+ }}
258
+ style={{
259
+ padding: "6px 15px",
260
+ margin: "0 1px",
261
+ background:
262
+ activeTab() === "animation" && !collapsed()
263
+ ? themeActiveTab
264
+ : "transparent",
265
+ color:
266
+ activeTab() === "animation" && !collapsed()
267
+ ? themeActiveText
268
+ : themeText,
269
+ border: "none",
270
+ cursor: "pointer",
271
+ "font-size": "12px",
272
+ "border-top":
273
+ activeTab() === "animation" && !collapsed()
274
+ ? "2px solid #3A72B0"
275
+ : "2px solid transparent",
276
+ "border-radius": "0",
277
+ }}
278
+ >
279
+ Animation
280
+ </button>
281
+ <button
282
+ onClick={() => {
283
+ setActiveTab("atlas");
284
+ setCollapsed(false);
285
+ }}
286
+ style={{
287
+ padding: "6px 15px",
288
+ margin: "0 1px",
289
+ background:
290
+ activeTab() === "atlas" && !collapsed()
291
+ ? themeActiveTab
292
+ : "transparent",
293
+ color:
294
+ activeTab() === "atlas" && !collapsed()
295
+ ? themeActiveText
296
+ : themeText,
297
+ border: "none",
298
+ cursor: "pointer",
299
+ "font-size": "12px",
300
+ "border-top":
301
+ activeTab() === "atlas" && !collapsed()
302
+ ? "2px solid #3A72B0"
303
+ : "2px solid transparent",
304
+ "border-radius": "0",
305
+ }}
306
+ >
307
+ SpriteSheet
308
+ </button>
309
+
310
+ {/* Spacer */}
311
+ <div style={{ flex: 1 }} />
312
+
313
+ {/* Recessed Brand Icon */}
314
+ <div
315
+ style={{
316
+ "margin-right": "15px",
317
+ "margin-bottom": "4px",
318
+ display: "flex",
319
+ "align-items": "center",
320
+ "user-select": "none",
321
+ opacity: 0.8,
322
+ "pointer-events": "none",
323
+ }}
324
+ >
325
+ <div
326
+ style={{
327
+ "font-weight": "bold",
328
+ "font-size": "13px",
329
+ color: "#7a7a7a",
330
+ "text-shadow": "0 1px 0 rgba(0, 0, 0, 0.12)",
331
+ display: "flex",
332
+ "align-items": "center",
333
+ gap: "6px",
334
+ "letter-spacing": "1.5px",
335
+ "font-family": "monospace",
336
+ }}
337
+ >
338
+ AETHER
339
+ <div
340
+ style={{
341
+ color: "#7a7a7a",
342
+ filter: "drop-shadow(0px 1px 0px rgba(0, 0, 0, 0.12))",
343
+ display: "flex",
344
+ }}
345
+ >
346
+ <IconAetherEngine size={18} />
347
+ </div>
348
+ </div>
349
+ </div>
350
+ </div>
351
+
352
+ {/* Body Area */}
353
+ <Show when={!collapsed()}>
354
+ <div
355
+ style={{
356
+ flex: 1,
357
+ padding: "5px",
358
+ overflow: "hidden",
359
+ position: "relative",
360
+ }}
361
+ >
362
+ <Show when={activeTab() === "scene"}>
363
+ <div
364
+ style={{
365
+ height: "100%",
366
+ "font-size": "13px",
367
+ padding: "10px",
368
+ display: "flex",
369
+ "flex-direction": "column",
370
+ "box-sizing": "border-box",
371
+ }}
372
+ >
373
+ <div
374
+ style={{
375
+ display: "flex",
376
+ "justify-content": "space-between",
377
+ "align-items": "center",
378
+ "margin-bottom": "10px",
379
+ "flex-shrink": 0,
380
+ }}
381
+ >
382
+ <div
383
+ style={{
384
+ "font-size": "16px",
385
+ "font-weight": "bold",
386
+ color: themeActiveText,
387
+ }}
388
+ >
389
+ Entity Explorer{" "}
390
+ <span style={{ color: "#888", "font-size": "13px" }}>
391
+ ({entities()} Active)
392
+ </span>
393
+ </div>
394
+ <button
395
+ onClick={addSprite}
396
+ style={{
397
+ background: "#3A72B0",
398
+ color: themeActiveText,
399
+ padding: "4px 12px",
400
+ border: `none`,
401
+ "border-radius": "3px",
402
+ cursor: "pointer",
403
+ "font-size": "12px",
404
+ }}
405
+ onMouseOver={(e) =>
406
+ (e.currentTarget.style.background = "#4682c0")
407
+ }
408
+ onMouseOut={(e) =>
409
+ (e.currentTarget.style.background = "#3A72B0")
410
+ }
411
+ >
412
+ + Add 2D Physics Sprite
413
+ </button>
414
+ </div>
415
+
416
+ <div style={{ flex: 1, "min-height": 0 }}>
417
+ <EntityExplorer engine={props.engine} />
418
+ </div>
419
+ </div>
420
+ </Show>
421
+
422
+ <Show when={activeTab() === "sprite"}>
423
+ <div style={{ height: "100%", width: "100%" }}>
424
+ <SpriteEditor engine={props.engine} embedded={true} />
425
+ </div>
426
+ </Show>
427
+
428
+ <Show when={activeTab() === "animation"}>
429
+ <div style={{ height: "100%", width: "100%" }}>
430
+ <AnimationEditor engine={props.engine} embedded={true} />
431
+ </div>
432
+ </Show>
433
+
434
+ <Show when={activeTab() === "atlas"}>
435
+ <div style={{ height: "100%", width: "100%" }}>
436
+ <SpriteSheetManager engine={props.engine} />
437
+ </div>
438
+ </Show>
439
+ </div>
440
+ </Show>
441
+ </div>
442
+ );
443
+ }