@skewedaspect/sage 0.3.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.
- package/LICENSE +21 -0
- package/Readme.md +53 -0
- package/dist/classes/bindings/toggle.d.ts +122 -0
- package/dist/classes/bindings/trigger.d.ts +79 -0
- package/dist/classes/bindings/value.d.ts +104 -0
- package/dist/classes/entity.d.ts +83 -0
- package/dist/classes/eventBus.d.ts +94 -0
- package/dist/classes/gameEngine.d.ts +57 -0
- package/dist/classes/input/gamepad.d.ts +94 -0
- package/dist/classes/input/keyboard.d.ts +66 -0
- package/dist/classes/input/mouse.d.ts +80 -0
- package/dist/classes/input/readers/gamepad.d.ts +77 -0
- package/dist/classes/input/readers/keyboard.d.ts +60 -0
- package/dist/classes/input/readers/mouse.d.ts +45 -0
- package/dist/classes/loggers/consoleBackend.d.ts +29 -0
- package/dist/classes/loggers/nullBackend.d.ts +14 -0
- package/dist/engines/scene.d.ts +11 -0
- package/dist/interfaces/action.d.ts +20 -0
- package/dist/interfaces/binding.d.ts +144 -0
- package/dist/interfaces/entity.d.ts +9 -0
- package/dist/interfaces/game.d.ts +26 -0
- package/dist/interfaces/input.d.ts +181 -0
- package/dist/interfaces/logger.d.ts +88 -0
- package/dist/managers/binding.d.ts +185 -0
- package/dist/managers/entity.d.ts +70 -0
- package/dist/managers/game.d.ts +20 -0
- package/dist/managers/input.d.ts +56 -0
- package/dist/managers/level.d.ts +55 -0
- package/dist/sage.d.ts +20 -0
- package/dist/sage.es.js +2208 -0
- package/dist/sage.es.js.map +1 -0
- package/dist/sage.umd.js +2 -0
- package/dist/sage.umd.js.map +1 -0
- package/dist/utils/capabilities.d.ts +2 -0
- package/dist/utils/graphics.d.ts +10 -0
- package/dist/utils/logger.d.ts +66 -0
- package/dist/utils/physics.d.ts +2 -0
- package/dist/utils/version.d.ts +5 -0
- package/docs/architecture.md +129 -0
- package/docs/behaviors.md +706 -0
- package/docs/binding_system.md +820 -0
- package/docs/design/input.md +86 -0
- package/docs/entity_system.md +538 -0
- package/docs/eventbus.md +225 -0
- package/docs/getting_started.md +264 -0
- package/docs/images/sage_logo.png +0 -0
- package/docs/images/sage_logo_shape.png +0 -0
- package/docs/overview.md +38 -0
- package/docs/physics_system.md +686 -0
- package/docs/scene_system.md +513 -0
- package/package.json +69 -0
- package/src/classes/bindings/toggle.ts +261 -0
- package/src/classes/bindings/trigger.ts +211 -0
- package/src/classes/bindings/value.ts +227 -0
- package/src/classes/entity.ts +256 -0
- package/src/classes/eventBus.ts +259 -0
- package/src/classes/gameEngine.ts +125 -0
- package/src/classes/input/gamepad.ts +388 -0
- package/src/classes/input/keyboard.ts +189 -0
- package/src/classes/input/mouse.ts +276 -0
- package/src/classes/input/readers/gamepad.ts +179 -0
- package/src/classes/input/readers/keyboard.ts +123 -0
- package/src/classes/input/readers/mouse.ts +133 -0
- package/src/classes/loggers/consoleBackend.ts +135 -0
- package/src/classes/loggers/nullBackend.ts +51 -0
- package/src/engines/scene.ts +112 -0
- package/src/images/sage_logo.svg +172 -0
- package/src/images/sage_logo_shape.svg +146 -0
- package/src/interfaces/action.ts +30 -0
- package/src/interfaces/binding.ts +191 -0
- package/src/interfaces/entity.ts +21 -0
- package/src/interfaces/game.ts +44 -0
- package/src/interfaces/input.ts +221 -0
- package/src/interfaces/logger.ts +118 -0
- package/src/managers/binding.ts +729 -0
- package/src/managers/entity.ts +252 -0
- package/src/managers/game.ts +111 -0
- package/src/managers/input.ts +233 -0
- package/src/managers/level.ts +261 -0
- package/src/sage.ts +119 -0
- package/src/types/global.d.ts +11 -0
- package/src/utils/capabilities.ts +16 -0
- package/src/utils/graphics.ts +148 -0
- package/src/utils/logger.ts +225 -0
- package/src/utils/physics.ts +16 -0
- package/src/utils/version.ts +11 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// Level Manager
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AbstractMesh,
|
|
7
|
+
AssetContainer,
|
|
8
|
+
Engine,
|
|
9
|
+
ISceneLoaderProgressEvent,
|
|
10
|
+
LoadAssetContainerAsync,
|
|
11
|
+
LoadSceneAsync,
|
|
12
|
+
Scene,
|
|
13
|
+
Sound,
|
|
14
|
+
Tags,
|
|
15
|
+
} from '@babylonjs/core';
|
|
16
|
+
import '@babylonjs/loaders'; // Ensure loaders (glTF, .babylon, etc.) are available
|
|
17
|
+
|
|
18
|
+
// Managers
|
|
19
|
+
import { GameEntityManager } from './entity.ts';
|
|
20
|
+
|
|
21
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export interface LevelLoadOptions
|
|
24
|
+
{
|
|
25
|
+
rootUrl : string;
|
|
26
|
+
filename : string;
|
|
27
|
+
useAssetContainer ?: boolean;
|
|
28
|
+
showLoadingUI ?: boolean;
|
|
29
|
+
onProgress ?: (percent : number) => void;
|
|
30
|
+
onSuccess ?: (scene : Scene) => void;
|
|
31
|
+
onError ?: (message : string, exception ?: any) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
/**
|
|
36
|
+
* Manages level loading and streaming using BabylonJS SceneLoader and AssetContainer.
|
|
37
|
+
* Also includes tagging for auto-detection (e.g. "Door", "Trigger", "Enemy"),
|
|
38
|
+
* and automatically spawns entities via an entity manager if an 'entityType' is found.
|
|
39
|
+
*
|
|
40
|
+
* Additionally, this version checks for audio metadata (e.g. sound files) and automatically
|
|
41
|
+
* creates BabylonJS Sound objects when found.
|
|
42
|
+
*/
|
|
43
|
+
export class LevelManager
|
|
44
|
+
{
|
|
45
|
+
private engine : Engine;
|
|
46
|
+
private currentScene : Scene | null = null;
|
|
47
|
+
private activeContainer : AssetContainer | null = null;
|
|
48
|
+
private entityManager : GameEntityManager;
|
|
49
|
+
|
|
50
|
+
constructor(engine : Engine, entityManager : GameEntityManager)
|
|
51
|
+
{
|
|
52
|
+
this.engine = engine;
|
|
53
|
+
this.entityManager = entityManager;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Loads a level, either as a full scene or into a container.
|
|
58
|
+
* @param options - Specifies rootUrl, filename, and optional callbacks.
|
|
59
|
+
*/
|
|
60
|
+
public async loadLevel(options : LevelLoadOptions) : Promise<void>
|
|
61
|
+
{
|
|
62
|
+
if(options.showLoadingUI)
|
|
63
|
+
{
|
|
64
|
+
this.engine.displayLoadingUI();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try
|
|
68
|
+
{
|
|
69
|
+
if(options.useAssetContainer)
|
|
70
|
+
{
|
|
71
|
+
// Load into AssetContainer (additive style)
|
|
72
|
+
const tempScene = this.currentScene ?? new Scene(this.engine);
|
|
73
|
+
const container = await LoadAssetContainerAsync(
|
|
74
|
+
options.filename,
|
|
75
|
+
tempScene,
|
|
76
|
+
{
|
|
77
|
+
rootUrl: options.rootUrl,
|
|
78
|
+
onProgress: (evt) => this.reportProgress(evt, options.onProgress),
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
this.activeContainer = container;
|
|
83
|
+
container.addAllToScene();
|
|
84
|
+
|
|
85
|
+
// Identify the final scene in use after adding to scene
|
|
86
|
+
// (meshes[0]?.getScene() ensures we get the correct scene reference)
|
|
87
|
+
const usedScene = container.meshes.length > 0
|
|
88
|
+
? container.meshes[0].getScene()
|
|
89
|
+
: tempScene;
|
|
90
|
+
|
|
91
|
+
this.autoTagAndEntityHook(container.meshes, usedScene);
|
|
92
|
+
|
|
93
|
+
if(options.onSuccess && this.currentScene)
|
|
94
|
+
{
|
|
95
|
+
options.onSuccess(this.currentScene);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
{
|
|
100
|
+
// Load as a new Scene (full replacement)
|
|
101
|
+
const scene = await LoadSceneAsync(
|
|
102
|
+
options.filename,
|
|
103
|
+
this.engine,
|
|
104
|
+
{
|
|
105
|
+
rootUrl: options.rootUrl,
|
|
106
|
+
onProgress: (evt) => this.reportProgress(evt, options.onProgress),
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Dispose any previous scene
|
|
111
|
+
this.disposeCurrentScene();
|
|
112
|
+
|
|
113
|
+
this.currentScene = scene;
|
|
114
|
+
// Now that we've loaded a fresh scene, do tagging & entity creation
|
|
115
|
+
this.autoTagAndEntityHook(scene.meshes, scene);
|
|
116
|
+
|
|
117
|
+
if(options.onSuccess)
|
|
118
|
+
{
|
|
119
|
+
options.onSuccess(scene);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error : any)
|
|
124
|
+
{
|
|
125
|
+
console.error('Level load failed:', error);
|
|
126
|
+
if(options.onError)
|
|
127
|
+
{
|
|
128
|
+
options.onError('Level loading failed.', error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
finally
|
|
132
|
+
{
|
|
133
|
+
if(options.showLoadingUI)
|
|
134
|
+
{
|
|
135
|
+
this.engine.hideLoadingUI();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Disposes the current scene and container (if any), freeing up memory.
|
|
142
|
+
*/
|
|
143
|
+
public disposeCurrentScene() : void
|
|
144
|
+
{
|
|
145
|
+
if(this.activeContainer)
|
|
146
|
+
{
|
|
147
|
+
this.activeContainer.removeAllFromScene();
|
|
148
|
+
this.activeContainer.dispose();
|
|
149
|
+
this.activeContainer = null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if(this.currentScene)
|
|
153
|
+
{
|
|
154
|
+
this.currentScene.dispose();
|
|
155
|
+
this.currentScene = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Returns the current active scene, if one is loaded.
|
|
161
|
+
*/
|
|
162
|
+
public getScene() : Scene | null
|
|
163
|
+
{
|
|
164
|
+
return this.currentScene;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Internal helper: Reports loading progress as a percentage.
|
|
169
|
+
* @param evt ISceneLoaderProgressEvent data
|
|
170
|
+
* @param callback optional progress callback
|
|
171
|
+
*/
|
|
172
|
+
private reportProgress(evt : ISceneLoaderProgressEvent, callback ?: (percent : number) => void) : void
|
|
173
|
+
{
|
|
174
|
+
if(!callback) { return; }
|
|
175
|
+
|
|
176
|
+
if(evt.lengthComputable && evt.total > 0)
|
|
177
|
+
{
|
|
178
|
+
const percent = (evt.loaded / evt.total) * 100;
|
|
179
|
+
callback(percent);
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
{
|
|
183
|
+
// If total size is unknown, just guess an arbitrary in-progress value
|
|
184
|
+
callback(50);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Automatically tag loaded meshes and create entities based on metadata.
|
|
190
|
+
* Also, if audio metadata is present, create spatial sounds.
|
|
191
|
+
*
|
|
192
|
+
* @param meshes An array of loaded AbstractMesh objects.
|
|
193
|
+
* @param scene The scene into which these meshes have been loaded.
|
|
194
|
+
*/
|
|
195
|
+
private autoTagAndEntityHook(meshes : AbstractMesh[], scene : Scene) : void
|
|
196
|
+
{
|
|
197
|
+
for(const mesh of meshes)
|
|
198
|
+
{
|
|
199
|
+
// Auto-tag common patterns by name
|
|
200
|
+
if(mesh.name)
|
|
201
|
+
{
|
|
202
|
+
if(mesh.name.startsWith('Door'))
|
|
203
|
+
{
|
|
204
|
+
Tags.AddTagsTo(mesh, 'Door');
|
|
205
|
+
}
|
|
206
|
+
if(mesh.name.startsWith('Trigger'))
|
|
207
|
+
{
|
|
208
|
+
Tags.AddTagsTo(mesh, 'Trigger');
|
|
209
|
+
}
|
|
210
|
+
if(mesh.name.startsWith('Enemy'))
|
|
211
|
+
{
|
|
212
|
+
Tags.AddTagsTo(mesh, 'EnemySpawn');
|
|
213
|
+
}
|
|
214
|
+
if(mesh.name.startsWith('SND_'))
|
|
215
|
+
{
|
|
216
|
+
Tags.AddTagsTo(mesh, 'AudioSource');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Ensure mesh.metadata exists
|
|
221
|
+
const metadata = mesh.metadata ?? (mesh.metadata = {});
|
|
222
|
+
|
|
223
|
+
// 1) Check for entityType (from .babylon or glTF extras)
|
|
224
|
+
const entityType = metadata.entityType || metadata?.gltf?.extras?.entityType;
|
|
225
|
+
if(entityType)
|
|
226
|
+
{
|
|
227
|
+
this.entityManager.createEntity(entityType, {
|
|
228
|
+
mesh,
|
|
229
|
+
position: mesh.position.clone(),
|
|
230
|
+
rotation: mesh.rotation.clone(),
|
|
231
|
+
metadata,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 2) Check for audio data: e.g. { soundFile: "AmbienceHum.wav", loop: true, autoplay: true }
|
|
236
|
+
// We also check if glTF extras has audio info.
|
|
237
|
+
const soundFile = metadata.soundFile ?? metadata?.gltf?.extras?.soundFile;
|
|
238
|
+
if(soundFile)
|
|
239
|
+
{
|
|
240
|
+
// Mark it as an audio source if not already
|
|
241
|
+
Tags.AddTagsTo(mesh, 'AudioSource');
|
|
242
|
+
|
|
243
|
+
// We'll read optional loop/autoplay from metadata too
|
|
244
|
+
const loop = metadata.loop ?? metadata?.gltf?.extras?.loop ?? false;
|
|
245
|
+
const autoplay = metadata.autoplay ?? metadata?.gltf?.extras?.autoplay ?? false;
|
|
246
|
+
const volume = metadata.volume ?? metadata?.gltf?.extras?.volume ?? 1.0;
|
|
247
|
+
|
|
248
|
+
const newSound = new Sound(`${ mesh.name }_sound`, soundFile, scene, null, {
|
|
249
|
+
loop: !!loop,
|
|
250
|
+
autoplay: !!autoplay,
|
|
251
|
+
spatialSound: true,
|
|
252
|
+
volume,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// If the mesh has a position, set the sound's 3D location
|
|
256
|
+
// (Note: if it's an empty or node, mesh.position will define the center)
|
|
257
|
+
newSound.setPosition(mesh.position);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
package/src/sage.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// SkewedAspect Game Engine
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
// Interfaces
|
|
6
|
+
import { GameCanvas, RenderEngineOptions, SageOptions } from './interfaces/game.ts';
|
|
7
|
+
|
|
8
|
+
// Base Classes
|
|
9
|
+
import { SkewedAspectGameEngine } from './classes/gameEngine.ts';
|
|
10
|
+
import { GameEventBus } from './classes/eventBus.ts';
|
|
11
|
+
|
|
12
|
+
// Engines
|
|
13
|
+
import { SceneEngine } from './engines/scene.ts';
|
|
14
|
+
|
|
15
|
+
// Managers
|
|
16
|
+
import { BindingManager } from './managers/binding.ts';
|
|
17
|
+
import { GameManager } from './managers/game.ts';
|
|
18
|
+
import { GameEntityManager } from './managers/entity.ts';
|
|
19
|
+
import { UserInputManager } from './managers/input.ts';
|
|
20
|
+
|
|
21
|
+
// Utils
|
|
22
|
+
import { createEngine } from './utils/graphics.ts';
|
|
23
|
+
import { createPhysics } from './utils/physics.ts';
|
|
24
|
+
import { LoggingUtility } from './utils/logger.ts';
|
|
25
|
+
import { VERSION } from './utils/version.ts';
|
|
26
|
+
|
|
27
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates an instance of SkewedAspectGameEngine.
|
|
31
|
+
* @param canvas - The game canvas.
|
|
32
|
+
* @param options - The game engine options including rendering options and log level.
|
|
33
|
+
* @returns A promise that resolves to an instance of SkewedAspectGameEngine.
|
|
34
|
+
*/
|
|
35
|
+
export async function createGameEngine(
|
|
36
|
+
canvas : GameCanvas,
|
|
37
|
+
options : SageOptions = {}
|
|
38
|
+
) : Promise<SkewedAspectGameEngine>
|
|
39
|
+
{
|
|
40
|
+
// Initialize logging
|
|
41
|
+
const logger = new LoggingUtility(options.logLevel || 'debug');
|
|
42
|
+
const engineLogger = logger.getLogger('SAGE');
|
|
43
|
+
|
|
44
|
+
engineLogger.info(`Initializing SAGE Game Engine v${ VERSION }...`);
|
|
45
|
+
|
|
46
|
+
// Initialize BabylonJS stuff
|
|
47
|
+
engineLogger.debug('Creating rendering engine...');
|
|
48
|
+
const engine = await createEngine(canvas, options.renderOptions || {});
|
|
49
|
+
|
|
50
|
+
engineLogger.debug('Creating physics engine...');
|
|
51
|
+
const physics = await createPhysics();
|
|
52
|
+
|
|
53
|
+
// Initialize internals
|
|
54
|
+
engineLogger.debug('Creating event bus...');
|
|
55
|
+
const eventBus = new GameEventBus(logger);
|
|
56
|
+
|
|
57
|
+
// Initialize SAGE Engines
|
|
58
|
+
engineLogger.debug('Creating scene engine...');
|
|
59
|
+
const sceneEngine = new SceneEngine(engine, physics, logger);
|
|
60
|
+
|
|
61
|
+
// Initialize SAGE Managers
|
|
62
|
+
engineLogger.debug('Creating managers...');
|
|
63
|
+
const inputManager = new UserInputManager(eventBus, canvas as HTMLElement, logger);
|
|
64
|
+
const bindingManager = new BindingManager(eventBus, logger);
|
|
65
|
+
const entityManager = new GameEntityManager(eventBus, logger, bindingManager);
|
|
66
|
+
const gameManager = new GameManager(engine, sceneEngine, entityManager, inputManager, logger);
|
|
67
|
+
|
|
68
|
+
engineLogger.info(`SAGE Game Engine v${ VERSION } initialized successfully.`);
|
|
69
|
+
|
|
70
|
+
// Register default input bindings
|
|
71
|
+
engineLogger.debug('Registering default input bindings...');
|
|
72
|
+
if(options.bindings)
|
|
73
|
+
{
|
|
74
|
+
for(const binding of options.bindings)
|
|
75
|
+
{
|
|
76
|
+
bindingManager.registerBinding(binding);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return new SkewedAspectGameEngine(
|
|
81
|
+
canvas,
|
|
82
|
+
engine,
|
|
83
|
+
physics,
|
|
84
|
+
eventBus,
|
|
85
|
+
logger,
|
|
86
|
+
|
|
87
|
+
// Engines
|
|
88
|
+
{
|
|
89
|
+
sceneEngine,
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Managers
|
|
93
|
+
{
|
|
94
|
+
bindingManager,
|
|
95
|
+
entityManager,
|
|
96
|
+
gameManager,
|
|
97
|
+
inputManager,
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
103
|
+
// Exports
|
|
104
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
export type { GameCanvas, RenderEngineOptions, SageOptions };
|
|
107
|
+
export type { GameEvent, GameEventCallback } from './classes/eventBus.ts';
|
|
108
|
+
export type { Action, AnalogAction, DigitalAction } from './interfaces/action.ts';
|
|
109
|
+
export type { GameEntityDefinition, GameEntityBehaviorConstructor } from './interfaces/entity.ts';
|
|
110
|
+
export type { LogLevel, LoggerInterface } from './interfaces/logger.ts';
|
|
111
|
+
|
|
112
|
+
// Export base classes
|
|
113
|
+
export { GameEventBus, SkewedAspectGameEngine };
|
|
114
|
+
export { GameEntity } from './classes/entity.ts';
|
|
115
|
+
export { LoggingUtility, ConsoleBackend, NullBackend } from './utils/logger.ts';
|
|
116
|
+
export { GameEntityBehavior } from './classes/entity.ts';
|
|
117
|
+
export { VERSION } from './utils/version.ts';
|
|
118
|
+
|
|
119
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// Global type declarations for the SAGE engine
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Global application version injected by Vite during build.
|
|
7
|
+
* This is read from package.json.
|
|
8
|
+
*/
|
|
9
|
+
declare const __APP_VERSION__ : string;
|
|
10
|
+
|
|
11
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// Utilities to test for capability of the current environment
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
export function isBrowser() : boolean
|
|
6
|
+
{
|
|
7
|
+
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function hasWebGPU() : boolean
|
|
11
|
+
{
|
|
12
|
+
return isBrowser()
|
|
13
|
+
&& !!window.navigator.gpu;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// Graphics Utility
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AbstractEngine,
|
|
7
|
+
Engine,
|
|
8
|
+
type EngineOptions,
|
|
9
|
+
NullEngine,
|
|
10
|
+
type NullEngineOptions,
|
|
11
|
+
WebGPUEngine,
|
|
12
|
+
type WebGPUEngineOptions,
|
|
13
|
+
} from '@babylonjs/core';
|
|
14
|
+
|
|
15
|
+
// Interfaces
|
|
16
|
+
import type { BabylonEngineOptions, GameCanvas, RenderEngineOptions } from '../interfaces/game.ts';
|
|
17
|
+
|
|
18
|
+
// Utilities
|
|
19
|
+
import { hasWebGPU } from './capabilities.ts';
|
|
20
|
+
|
|
21
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Builds and initializes a WebGPU engine.
|
|
25
|
+
*
|
|
26
|
+
* @param canvas - The canvas element to render on (HTMLCanvasElement or OffscreenCanvas)
|
|
27
|
+
* @param options - Options specific to the WebGPU engine
|
|
28
|
+
* @returns A promise that resolves to an initialized WebGPUEngine
|
|
29
|
+
*/
|
|
30
|
+
async function _buildWebGPUEngine(
|
|
31
|
+
canvas : HTMLCanvasElement | OffscreenCanvas,
|
|
32
|
+
options : WebGPUEngineOptions
|
|
33
|
+
) : Promise<WebGPUEngine>
|
|
34
|
+
{
|
|
35
|
+
const engine = new WebGPUEngine(canvas, options);
|
|
36
|
+
await engine.initAsync();
|
|
37
|
+
return engine;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Builds a WebGL engine.
|
|
42
|
+
*
|
|
43
|
+
* @param canvas - The canvas element to render on (GameCanvas)
|
|
44
|
+
* @param options - Options for configuring the engine
|
|
45
|
+
* @returns An initialized WebGL Engine
|
|
46
|
+
*/
|
|
47
|
+
function _buildWebGLEngine(canvas : GameCanvas, options : BabylonEngineOptions) : Engine
|
|
48
|
+
{
|
|
49
|
+
return new Engine(canvas, options.antialias, options.options, options.adaptToDeviceRatio);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Builds a Null engine, which is used when no rendering is required.
|
|
54
|
+
*
|
|
55
|
+
* @param options - Options specific to the Null engine
|
|
56
|
+
* @returns An initialized Null Engine
|
|
57
|
+
*/
|
|
58
|
+
function _buildNullEngine(options : NullEngineOptions) : NullEngine
|
|
59
|
+
{
|
|
60
|
+
return new NullEngine(options as NullEngineOptions);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates an appropriate engine based on the provided canvas and options.
|
|
67
|
+
*
|
|
68
|
+
* @param canvas - The canvas element to render on, or null for a Null engine
|
|
69
|
+
* @param options - Options for configuring the engine (EngineOptions, WebGPUEngineOptions, or NullEngineOptions)
|
|
70
|
+
* @returns A promise that resolves to an initialized AbstractEngine
|
|
71
|
+
*/
|
|
72
|
+
export async function createEngine(
|
|
73
|
+
canvas : GameCanvas | null,
|
|
74
|
+
options : RenderEngineOptions
|
|
75
|
+
) : Promise<AbstractEngine>
|
|
76
|
+
{
|
|
77
|
+
// Check if we should use a null engine (no canvas provided)
|
|
78
|
+
if(canvas === null)
|
|
79
|
+
{
|
|
80
|
+
console.debug('Using Null Engine');
|
|
81
|
+
return _buildNullEngine(options as NullEngineOptions);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Extract the forceEngine option if available
|
|
85
|
+
const forceEngine = options.forceEngine || 'auto';
|
|
86
|
+
|
|
87
|
+
// Force WebGPU engine if specified and available
|
|
88
|
+
if(forceEngine === 'webgpu')
|
|
89
|
+
{
|
|
90
|
+
if(hasWebGPU())
|
|
91
|
+
{
|
|
92
|
+
try
|
|
93
|
+
{
|
|
94
|
+
console.debug('Using forced WebGPU engine');
|
|
95
|
+
return await _buildWebGPUEngine(canvas, options);
|
|
96
|
+
}
|
|
97
|
+
catch (error)
|
|
98
|
+
{
|
|
99
|
+
console.error('Forced WebGPU initialization failed:', error);
|
|
100
|
+
throw new Error(
|
|
101
|
+
'Forced WebGPU failed to initialize. If WebGPU is required, check browser compatibility.'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
{
|
|
107
|
+
throw new Error('WebGPU was forced but is not available in this browser.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Force WebGL engine if specified
|
|
112
|
+
if(forceEngine === 'webgl')
|
|
113
|
+
{
|
|
114
|
+
console.debug('Using forced WebGL engine');
|
|
115
|
+
return _buildWebGLEngine(canvas, options as BabylonEngineOptions);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Auto detection (default behavior)
|
|
119
|
+
if(hasWebGPU())
|
|
120
|
+
{
|
|
121
|
+
try
|
|
122
|
+
{
|
|
123
|
+
console.debug('Using WebGPU');
|
|
124
|
+
|
|
125
|
+
return _buildWebGPUEngine(canvas, options)
|
|
126
|
+
.catch((error) =>
|
|
127
|
+
{
|
|
128
|
+
console.warn('WebGPU initialization failed, falling back to WebGL:', error);
|
|
129
|
+
return _buildWebGLEngine(canvas, options as EngineOptions);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (error)
|
|
133
|
+
{
|
|
134
|
+
console.warn('WebGPU initialization failed, falling back to WebGL:', error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
{
|
|
139
|
+
console.warn('WebGPU not supported, falling back to WebGL.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.debug('Using WebGL');
|
|
143
|
+
|
|
144
|
+
return _buildWebGLEngine(canvas, options as EngineOptions);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
148
|
+
|