@series-inc/stowkit-phaser-loader 0.1.18

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.
@@ -0,0 +1,638 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@series-inc/stowkit-reader')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', '@series-inc/stowkit-reader'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.StowKitPhaserLoader = {}, global.StowKitReader));
5
+ })(this, (function (exports, stowkitReader) { 'use strict';
6
+
7
+ /**
8
+ * BasisTranscoder - Manually decode KTX2 textures to WebGL textures
9
+ * Used for Phaser which doesn't have built-in KTX2 support
10
+ */
11
+ // Basis transcoder formats (from Three.js KTX2Loader.TranscoderFormat)
12
+ const BASIS_FORMAT = {
13
+ ETC1: 0,
14
+ ETC2: 1,
15
+ BC1: 2, // DXT1
16
+ BC3: 3, // DXT5
17
+ BC7_M5: 7, // BC7
18
+ PVRTC1_4_RGB: 8,
19
+ PVRTC1_4_RGBA: 9,
20
+ ASTC_4x4: 10,
21
+ RGBA32: 13};
22
+ class BasisTranscoder {
23
+ constructor(basisPath = '/basis/') {
24
+ this.module = null;
25
+ this.initialized = false;
26
+ this.initPromise = null;
27
+ this.basisPath = basisPath;
28
+ }
29
+ /**
30
+ * Initialize the Basis Universal transcoder
31
+ */
32
+ async init() {
33
+ if (this.initialized)
34
+ return;
35
+ if (this.initPromise)
36
+ return this.initPromise;
37
+ this.initPromise = (async () => {
38
+ try {
39
+ // Load the basis transcoder
40
+ const scriptPath = `${this.basisPath}basis_transcoder.js`;
41
+ // Import the basis module
42
+ const BasisModule = await this.loadBasisModule(scriptPath);
43
+ // Initialize
44
+ BasisModule.initializeBasis();
45
+ this.module = BasisModule;
46
+ this.initialized = true;
47
+ }
48
+ catch (error) {
49
+ console.error('[BasisTranscoder] Failed to initialize:', error);
50
+ throw error;
51
+ }
52
+ })();
53
+ return this.initPromise;
54
+ }
55
+ /**
56
+ * Load the basis transcoder module
57
+ */
58
+ async loadBasisModule(scriptPath) {
59
+ return new Promise((resolve, reject) => {
60
+ const script = document.createElement('script');
61
+ script.src = scriptPath;
62
+ script.async = true;
63
+ script.onload = () => {
64
+ // The basis transcoder exposes a global BASIS function
65
+ if (typeof window.BASIS === 'function') {
66
+ window.BASIS().then((module) => {
67
+ resolve(module);
68
+ }).catch(reject);
69
+ }
70
+ else {
71
+ reject(new Error('BASIS module not found'));
72
+ }
73
+ };
74
+ script.onerror = () => {
75
+ reject(new Error(`Failed to load ${scriptPath}`));
76
+ };
77
+ document.head.appendChild(script);
78
+ });
79
+ }
80
+ /**
81
+ * Transcode KTX2 data to a WebGL-compatible format
82
+ */
83
+ async transcodeKTX2(data, gl) {
84
+ if (!this.initialized) {
85
+ await this.init();
86
+ }
87
+ if (!this.module) {
88
+ throw new Error('Basis module not initialized');
89
+ }
90
+ // Create KTX2File from data
91
+ const ktx2File = new this.module.KTX2File(data);
92
+ try {
93
+ // Validate file
94
+ if (!ktx2File.isValid()) {
95
+ throw new Error('Invalid or unsupported KTX2 file');
96
+ }
97
+ // Get image dimensions
98
+ const width = ktx2File.getWidth();
99
+ const height = ktx2File.getHeight();
100
+ // Check if the texture has alpha
101
+ const hasAlpha = ktx2File.getHasAlpha();
102
+ // Check format type (UASTC or ETC1S)
103
+ const isUASTC = ktx2File.isUASTC();
104
+ const isETC1S = ktx2File.isETC1S();
105
+ // Start transcoding
106
+ if (!ktx2File.startTranscoding()) {
107
+ throw new Error('Failed to start KTX2 transcoding');
108
+ }
109
+ // Use first mip level, first layer, first face
110
+ const mip = 0;
111
+ const layer = 0;
112
+ const face = 0;
113
+ // Detect best format for this device and file type
114
+ const targetFormat = this.detectBestFormat(gl, hasAlpha, isETC1S);
115
+ // Get transcoded size
116
+ const transcodedSize = ktx2File.getImageTranscodedSizeInBytes(mip, layer, face, targetFormat.basisFormat);
117
+ // Allocate output buffer
118
+ const transcodedData = new Uint8Array(transcodedSize);
119
+ // Transcode (parameters: dst, mip, layer, face, format, unused1, unused2, unused3)
120
+ const result = ktx2File.transcodeImage(transcodedData, mip, layer, face, targetFormat.basisFormat, 0, -1, -1);
121
+ if (result === 0) {
122
+ throw new Error('KTX2 transcoding failed');
123
+ }
124
+ return {
125
+ data: transcodedData,
126
+ width,
127
+ height,
128
+ format: targetFormat.glFormat,
129
+ internalFormat: targetFormat.glInternalFormat,
130
+ compressed: targetFormat.compressed
131
+ };
132
+ }
133
+ finally {
134
+ ktx2File.close();
135
+ ktx2File.delete();
136
+ }
137
+ }
138
+ /**
139
+ * Detect the best compression format supported by the device
140
+ */
141
+ detectBestFormat(gl, hasAlpha = true, isETC1S = false) {
142
+ const isWebGL2 = gl instanceof WebGL2RenderingContext;
143
+ // Check for extensions
144
+ const s3tc = gl.getExtension('WEBGL_compressed_texture_s3tc') ||
145
+ gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
146
+ const bptc = gl.getExtension('EXT_texture_compression_bptc');
147
+ const etc1 = gl.getExtension('WEBGL_compressed_texture_etc1');
148
+ const etc = gl.getExtension('WEBGL_compressed_texture_etc');
149
+ const astc = gl.getExtension('WEBGL_compressed_texture_astc');
150
+ const pvrtc = gl.getExtension('WEBGL_compressed_texture_pvrtc') ||
151
+ gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
152
+ // For ETC1S: Use BC1 (no alpha support in ETC1S)
153
+ // For UASTC with alpha: Use BC3/BC7
154
+ // For UASTC without alpha: Use BC1
155
+ // BC7 on desktop with WebGL2 (requires separate BPTC extension)
156
+ // Only use for UASTC with alpha
157
+ if (isWebGL2 && bptc && hasAlpha && !isETC1S) {
158
+ return {
159
+ basisFormat: BASIS_FORMAT.BC7_M5,
160
+ glFormat: gl.RGBA,
161
+ glInternalFormat: bptc.COMPRESSED_RGBA_BPTC_UNORM_EXT,
162
+ compressed: true
163
+ };
164
+ }
165
+ // BC3 (DXT5) on desktop - only for textures with alpha (UASTC)
166
+ if (s3tc && hasAlpha && !isETC1S) {
167
+ return {
168
+ basisFormat: BASIS_FORMAT.BC3,
169
+ glFormat: gl.RGBA,
170
+ glInternalFormat: s3tc.COMPRESSED_RGBA_S3TC_DXT5_EXT,
171
+ compressed: true
172
+ };
173
+ }
174
+ // BC1 (DXT1) on desktop - for ETC1S or textures without alpha
175
+ if (s3tc) {
176
+ return {
177
+ basisFormat: BASIS_FORMAT.BC1,
178
+ glFormat: gl.RGB,
179
+ glInternalFormat: s3tc.COMPRESSED_RGB_S3TC_DXT1_EXT,
180
+ compressed: true
181
+ };
182
+ }
183
+ // ASTC on mobile (best quality)
184
+ if (astc) {
185
+ return {
186
+ basisFormat: BASIS_FORMAT.ASTC_4x4,
187
+ glFormat: gl.RGBA,
188
+ glInternalFormat: astc.COMPRESSED_RGBA_ASTC_4x4_KHR,
189
+ compressed: true
190
+ };
191
+ }
192
+ // ETC2 on mobile with WebGL2
193
+ if (isWebGL2 && etc) {
194
+ return {
195
+ basisFormat: BASIS_FORMAT.ETC2,
196
+ glFormat: gl.RGBA,
197
+ glInternalFormat: etc.COMPRESSED_RGBA8_ETC2_EAC || 0x9278,
198
+ compressed: true
199
+ };
200
+ }
201
+ // ETC1 on mobile
202
+ if (etc1 || etc) {
203
+ return {
204
+ basisFormat: BASIS_FORMAT.ETC1,
205
+ glFormat: gl.RGB,
206
+ glInternalFormat: etc1?.COMPRESSED_RGB_ETC1_WEBGL || 0x8D64,
207
+ compressed: true
208
+ };
209
+ }
210
+ // PVRTC on iOS (last resort for compressed)
211
+ if (pvrtc) {
212
+ return {
213
+ basisFormat: hasAlpha ? BASIS_FORMAT.PVRTC1_4_RGBA : BASIS_FORMAT.PVRTC1_4_RGB,
214
+ glFormat: gl.RGBA,
215
+ glInternalFormat: pvrtc.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
216
+ compressed: true
217
+ };
218
+ }
219
+ // Fallback to uncompressed RGBA
220
+ return {
221
+ basisFormat: BASIS_FORMAT.RGBA32,
222
+ glFormat: gl.RGBA,
223
+ glInternalFormat: gl.RGBA,
224
+ compressed: false
225
+ };
226
+ }
227
+ /**
228
+ * Dispose resources
229
+ */
230
+ dispose() {
231
+ this.module = null;
232
+ this.initialized = false;
233
+ this.initPromise = null;
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Represents an opened StowKit pack for Phaser
239
+ * Supports loading images and audio only (no 3D models)
240
+ */
241
+ class StowKitPhaserPack {
242
+ constructor(reader, transcoder, gl) {
243
+ this.textureCache = new Map();
244
+ this.transcodedDataCache = new Map();
245
+ this.reader = reader;
246
+ this.transcoder = transcoder;
247
+ this.tempGl = gl;
248
+ }
249
+ /**
250
+ * Load a texture by its canonical path/name
251
+ * Returns a Phaser texture
252
+ */
253
+ async loadTexture(assetPath, scene) {
254
+ const totalStart = performance.now();
255
+ // Check cache first
256
+ if (this.textureCache.has(assetPath)) {
257
+ stowkitReader.PerfLogger.log(`[Perf] Texture cache hit: ${assetPath}`);
258
+ return this.textureCache.get(assetPath);
259
+ }
260
+ // Find asset by path
261
+ const assetIndex = this.reader.findAssetByPath(assetPath);
262
+ if (assetIndex < 0) {
263
+ throw new Error(`Texture not found: ${assetPath}`);
264
+ }
265
+ // Verify it's a texture
266
+ const info = this.reader.getAssetInfo(assetIndex);
267
+ if (!info || info.type !== stowkitReader.AssetType.TEXTURE_2D) {
268
+ throw new Error(`Asset is not a texture: ${assetPath}`);
269
+ }
270
+ // Use the same logic as getPhaserTexture but with custom key
271
+ const key = assetPath;
272
+ const gl = scene.sys.game.renderer.gl;
273
+ // Read and transcode
274
+ const data = this.reader.readAssetData(assetIndex);
275
+ if (!data) {
276
+ throw new Error(`Failed to read texture data for ${assetPath}`);
277
+ }
278
+ const transcodeStart = performance.now();
279
+ const transcoded = await this.transcoder.transcodeKTX2(data, gl);
280
+ stowkitReader.PerfLogger.log(`[Perf] Phaser Basis transcode: ${(performance.now() - transcodeStart).toFixed(2)}ms (${transcoded.width}x${transcoded.height})`);
281
+ // Format like Phaser's KTX parser
282
+ const compressedTextureData = {
283
+ mipmaps: [{
284
+ data: transcoded.data,
285
+ width: transcoded.width,
286
+ height: transcoded.height
287
+ }],
288
+ width: transcoded.width,
289
+ height: transcoded.height,
290
+ internalFormat: transcoded.internalFormat,
291
+ compressed: transcoded.compressed,
292
+ generateMipmap: false,
293
+ format: transcoded.internalFormat
294
+ };
295
+ const phaserTexture = scene.textures.addCompressedTexture(key, compressedTextureData);
296
+ this.textureCache.set(assetPath, phaserTexture);
297
+ stowkitReader.PerfLogger.log(`[Perf] ===== Total Phaser texture load: ${(performance.now() - totalStart).toFixed(2)}ms =====`);
298
+ return phaserTexture;
299
+ }
300
+ /**
301
+ * Get a Phaser texture by index (for previews/demos)
302
+ * Creates compressed texture directly in Phaser's GL context
303
+ */
304
+ async getPhaserTexture(index, scene) {
305
+ const key = `texture_${index}`;
306
+ // Check if already exists in Phaser's texture manager
307
+ if (scene.textures.exists(key)) {
308
+ return scene.textures.get(key);
309
+ }
310
+ // Get Phaser's GL context
311
+ const gl = scene.sys.game.renderer.gl;
312
+ // Read and transcode the KTX2 data
313
+ const data = this.reader.readAssetData(index);
314
+ if (!data) {
315
+ throw new Error(`Failed to read texture data for index ${index}`);
316
+ }
317
+ // Transcode to compressed format (don't create the WebGL texture yet - let Phaser do it)
318
+ const transcoded = await this.transcoder.transcodeKTX2(data, gl);
319
+ // Format the data exactly like Phaser's KTX parser output
320
+ const compressedTextureData = {
321
+ mipmaps: [{
322
+ data: transcoded.data,
323
+ width: transcoded.width,
324
+ height: transcoded.height
325
+ }],
326
+ width: transcoded.width,
327
+ height: transcoded.height,
328
+ internalFormat: transcoded.internalFormat,
329
+ compressed: transcoded.compressed,
330
+ generateMipmap: false,
331
+ format: transcoded.internalFormat // Phaser uses this for compressionAlgorithm
332
+ };
333
+ // Add to Phaser's texture manager as a compressed texture
334
+ const phaserTexture = scene.textures.addCompressedTexture(key, compressedTextureData);
335
+ // Cache it
336
+ this.textureCache.set(key, phaserTexture);
337
+ return phaserTexture;
338
+ }
339
+ /**
340
+ * Load texture by index into a specific GL context
341
+ */
342
+ async loadTextureInContext(index, gl) {
343
+ // Read texture data
344
+ const data = this.reader.readAssetData(index);
345
+ if (!data) {
346
+ throw new Error(`Failed to read texture data for index ${index}`);
347
+ }
348
+ // Get metadata - all textures in .stow files are KTX2 format
349
+ const metadata = this.reader.parseTextureMetadata(index);
350
+ const isKtx2 = metadata?.channelFormat !== undefined;
351
+ if (isKtx2) {
352
+ // Transcode KTX2 to WebGL texture using the provided context
353
+ const transcoded = await this.transcoder.transcodeKTX2(data, gl);
354
+ // Create WebGL texture in the provided context
355
+ const texture = gl.createTexture();
356
+ if (!texture) {
357
+ throw new Error('Failed to create WebGL texture');
358
+ }
359
+ gl.bindTexture(gl.TEXTURE_2D, texture);
360
+ if (transcoded.compressed) {
361
+ gl.compressedTexImage2D(gl.TEXTURE_2D, 0, transcoded.internalFormat, transcoded.width, transcoded.height, 0, transcoded.data);
362
+ }
363
+ else {
364
+ gl.texImage2D(gl.TEXTURE_2D, 0, transcoded.internalFormat, transcoded.width, transcoded.height, 0, transcoded.format, gl.UNSIGNED_BYTE, transcoded.data);
365
+ }
366
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
367
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
368
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
369
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
370
+ gl.bindTexture(gl.TEXTURE_2D, null);
371
+ return {
372
+ texture,
373
+ width: transcoded.width,
374
+ height: transcoded.height
375
+ };
376
+ }
377
+ else {
378
+ // Handle uncompressed image data
379
+ return await this.loadUncompressedTextureInContext(data, gl);
380
+ }
381
+ }
382
+ /**
383
+ * Load uncompressed image (PNG, JPEG) as WebGL texture in a specific context
384
+ */
385
+ async loadUncompressedTextureInContext(data, gl) {
386
+ return new Promise((resolve, reject) => {
387
+ const blob = new Blob([data.buffer]);
388
+ const url = URL.createObjectURL(blob);
389
+ const img = new Image();
390
+ img.onload = () => {
391
+ const texture = gl.createTexture();
392
+ if (!texture) {
393
+ URL.revokeObjectURL(url);
394
+ reject(new Error('Failed to create WebGL texture'));
395
+ return;
396
+ }
397
+ gl.bindTexture(gl.TEXTURE_2D, texture);
398
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
399
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
400
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
401
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
402
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
403
+ gl.bindTexture(gl.TEXTURE_2D, null);
404
+ URL.revokeObjectURL(url);
405
+ resolve({
406
+ texture,
407
+ width: img.width,
408
+ height: img.height
409
+ });
410
+ };
411
+ img.onerror = () => {
412
+ URL.revokeObjectURL(url);
413
+ reject(new Error('Failed to load image'));
414
+ };
415
+ img.src = url;
416
+ });
417
+ }
418
+ /**
419
+ * Load audio by its canonical path/name
420
+ * Returns an AudioBuffer
421
+ */
422
+ async loadAudio(assetPath, audioContext) {
423
+ // Find asset by path
424
+ const assetIndex = this.reader.findAssetByPath(assetPath);
425
+ if (assetIndex < 0) {
426
+ throw new Error(`Audio not found: ${assetPath}`);
427
+ }
428
+ // Verify it's audio
429
+ const info = this.reader.getAssetInfo(assetIndex);
430
+ if (!info || info.type !== stowkitReader.AssetType.AUDIO) {
431
+ throw new Error(`Asset is not audio: ${assetPath}`);
432
+ }
433
+ return await this.loadAudioByIndex(assetIndex, audioContext);
434
+ }
435
+ /**
436
+ * Load audio by index
437
+ */
438
+ async loadAudioByIndex(index, audioContext) {
439
+ const data = this.reader.readAssetData(index);
440
+ if (!data) {
441
+ throw new Error(`Failed to read audio data for index ${index}`);
442
+ }
443
+ // Use provided context or create a new one
444
+ const ctx = audioContext || new AudioContext();
445
+ // Decode audio data
446
+ const arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
447
+ const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
448
+ return audioBuffer;
449
+ }
450
+ /**
451
+ * Create an HTML audio element for preview
452
+ */
453
+ async createAudioPreview(index) {
454
+ const data = this.reader.readAssetData(index);
455
+ if (!data) {
456
+ throw new Error(`Failed to read audio for index ${index}`);
457
+ }
458
+ const blob = new Blob([data.buffer], { type: 'audio/mp4' });
459
+ const url = URL.createObjectURL(blob);
460
+ const audio = document.createElement('audio');
461
+ audio.controls = true;
462
+ audio.src = url;
463
+ audio.addEventListener('ended', () => URL.revokeObjectURL(url));
464
+ audio.addEventListener('error', () => URL.revokeObjectURL(url));
465
+ return audio;
466
+ }
467
+ /**
468
+ * Get list of all assets in pack
469
+ */
470
+ listAssets() {
471
+ return this.reader.listAssets();
472
+ }
473
+ /**
474
+ * Get asset count
475
+ */
476
+ getAssetCount() {
477
+ return this.reader.getAssetCount();
478
+ }
479
+ /**
480
+ * Get asset info by index
481
+ */
482
+ getAssetInfo(index) {
483
+ return this.reader.getAssetInfo(index);
484
+ }
485
+ /**
486
+ * Get texture metadata
487
+ */
488
+ getTextureMetadata(index) {
489
+ return this.reader.parseTextureMetadata(index);
490
+ }
491
+ /**
492
+ * Get audio metadata
493
+ */
494
+ getAudioMetadata(index) {
495
+ return this.reader.parseAudioMetadata(index);
496
+ }
497
+ /**
498
+ * Close the pack and free resources
499
+ */
500
+ dispose() {
501
+ // Clear texture cache (Phaser manages texture disposal)
502
+ this.textureCache.clear();
503
+ this.transcodedDataCache.clear();
504
+ // Close reader
505
+ this.reader.close();
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Phaser loader for StowKit asset packs
511
+ * Supports images and audio only (no 3D models)
512
+ *
513
+ * Usage:
514
+ * ```typescript
515
+ * const pack = await StowKitPhaserLoader.load('assets.stow');
516
+ * const texture = await pack.loadTexture('textures/player');
517
+ * const audio = await pack.loadAudio('sounds/bgm');
518
+ * ```
519
+ */
520
+ class StowKitPhaserLoader {
521
+ /**
522
+ * Load a .stow pack file from a URL
523
+ */
524
+ static async load(url, options) {
525
+ // Initialize loaders if needed
526
+ if (!this.initialized) {
527
+ await this.initialize(options);
528
+ }
529
+ // Fetch the pack file
530
+ const response = await fetch(url);
531
+ if (!response.ok) {
532
+ throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
533
+ }
534
+ const arrayBuffer = await response.arrayBuffer();
535
+ // Create a new reader instance for this pack
536
+ const reader = new stowkitReader.StowKitReader(this.wasmPath);
537
+ await reader.init();
538
+ await reader.open(arrayBuffer);
539
+ // Return pack wrapper with its own dedicated reader
540
+ return new StowKitPhaserPack(reader, this.transcoder, this.gl);
541
+ }
542
+ /**
543
+ * Load a .stow pack from memory (ArrayBuffer, Blob, or File)
544
+ */
545
+ static async loadFromMemory(data, options) {
546
+ // Initialize loaders if needed
547
+ if (!this.initialized) {
548
+ await this.initialize(options);
549
+ }
550
+ // Convert to ArrayBuffer if needed
551
+ let arrayBuffer;
552
+ if (data instanceof ArrayBuffer) {
553
+ arrayBuffer = data;
554
+ }
555
+ else if (typeof Blob !== 'undefined' && data instanceof Blob) {
556
+ arrayBuffer = await data.arrayBuffer();
557
+ }
558
+ else if ('arrayBuffer' in data && typeof data.arrayBuffer === 'function') {
559
+ arrayBuffer = await data.arrayBuffer();
560
+ }
561
+ else {
562
+ throw new Error('Data must be ArrayBuffer, Blob, or File');
563
+ }
564
+ // Create a new reader instance for this pack
565
+ const reader = new stowkitReader.StowKitReader(this.wasmPath);
566
+ await reader.init();
567
+ await reader.open(arrayBuffer);
568
+ // Return pack wrapper with its own dedicated reader
569
+ return new StowKitPhaserPack(reader, this.transcoder, this.gl);
570
+ }
571
+ /**
572
+ * Initialize the loader (called automatically on first load)
573
+ */
574
+ static async initialize(options) {
575
+ this.wasmPath = options?.wasmPath || '/stowkit/stowkit_reader.wasm';
576
+ const basisPath = options?.basisPath || '/basis/';
577
+ // Get or create WebGL context (shared across all packs)
578
+ if (options?.gl) {
579
+ this.gl = options.gl;
580
+ this.ownGl = false;
581
+ }
582
+ else {
583
+ // Create a temporary canvas for WebGL operations
584
+ const canvas = document.createElement('canvas');
585
+ canvas.width = 1;
586
+ canvas.height = 1;
587
+ canvas.style.display = 'none';
588
+ document.body.appendChild(canvas);
589
+ this.gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
590
+ if (!this.gl) {
591
+ throw new Error('Failed to create WebGL context');
592
+ }
593
+ this.ownGl = true;
594
+ }
595
+ // Initialize basis transcoder (shared across all packs)
596
+ this.transcoder = new BasisTranscoder(basisPath);
597
+ await this.transcoder.init();
598
+ this.initialized = true;
599
+ }
600
+ /**
601
+ * Dispose of shared resources
602
+ */
603
+ static dispose() {
604
+ if (this.transcoder) {
605
+ this.transcoder.dispose();
606
+ this.transcoder = null;
607
+ }
608
+ // Only dispose GL if we created it
609
+ if (this.gl && this.ownGl) {
610
+ const canvas = this.gl.canvas;
611
+ if (canvas && canvas.parentNode) {
612
+ canvas.parentNode.removeChild(canvas);
613
+ }
614
+ this.gl = null;
615
+ }
616
+ this.initialized = false;
617
+ }
618
+ }
619
+ StowKitPhaserLoader.transcoder = null;
620
+ StowKitPhaserLoader.initialized = false;
621
+ StowKitPhaserLoader.gl = null;
622
+ StowKitPhaserLoader.ownGl = false;
623
+ StowKitPhaserLoader.wasmPath = '/stowkit/stowkit_reader.wasm';
624
+
625
+ Object.defineProperty(exports, "AssetType", {
626
+ enumerable: true,
627
+ get: function () { return stowkitReader.AssetType; }
628
+ });
629
+ Object.defineProperty(exports, "PerfLogger", {
630
+ enumerable: true,
631
+ get: function () { return stowkitReader.PerfLogger; }
632
+ });
633
+ exports.BasisTranscoder = BasisTranscoder;
634
+ exports.StowKitPhaserLoader = StowKitPhaserLoader;
635
+ exports.StowKitPhaserPack = StowKitPhaserPack;
636
+
637
+ }));
638
+ //# sourceMappingURL=stowkit-phaser-loader.js.map