@loaders.gl/textures 4.4.0-alpha.2 → 4.4.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 (234) hide show
  1. package/README.md +197 -1
  2. package/dist/basis-loader.d.ts +15 -10
  3. package/dist/basis-loader.d.ts.map +1 -1
  4. package/dist/basis-loader.js +1 -1
  5. package/dist/basis-loader.js.map +1 -0
  6. package/dist/basis-worker-node.js +932 -10040
  7. package/dist/basis-worker.d.ts +2 -0
  8. package/dist/basis-worker.d.ts.map +1 -0
  9. package/dist/basis-worker.js +337 -151
  10. package/dist/basis-worker.js.map +1 -0
  11. package/dist/compressed-texture-loader.d.ts +2 -5
  12. package/dist/compressed-texture-loader.d.ts.map +1 -1
  13. package/dist/compressed-texture-loader.js +2 -3
  14. package/dist/compressed-texture-loader.js.map +1 -0
  15. package/dist/compressed-texture-worker.d.ts +2 -0
  16. package/dist/compressed-texture-worker.d.ts.map +1 -0
  17. package/dist/compressed-texture-worker.js +1148 -365
  18. package/dist/compressed-texture-worker.js.map +1 -0
  19. package/dist/compressed-texture-writer.js +1 -0
  20. package/dist/compressed-texture-writer.js.map +1 -0
  21. package/dist/crunch-loader.d.ts +1 -5
  22. package/dist/crunch-loader.d.ts.map +1 -1
  23. package/dist/crunch-loader.js +2 -3
  24. package/dist/crunch-loader.js.map +1 -0
  25. package/dist/crunch-worker.d.ts +2 -0
  26. package/dist/crunch-worker.d.ts.map +1 -0
  27. package/dist/crunch-worker.js +204 -92
  28. package/dist/crunch-worker.js.map +1 -0
  29. package/dist/dist.dev.js +2687 -677
  30. package/dist/dist.min.js +1 -2
  31. package/dist/index.cjs +1644 -428
  32. package/dist/index.cjs.map +4 -4
  33. package/dist/index.d.ts +13 -4
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -3
  36. package/dist/index.js.map +1 -0
  37. package/dist/ktx2-basis-writer-worker-node.js +574 -9832
  38. package/dist/ktx2-basis-writer-worker.d.ts +2 -0
  39. package/dist/ktx2-basis-writer-worker.d.ts.map +1 -0
  40. package/dist/ktx2-basis-writer-worker.js +45 -7
  41. package/dist/ktx2-basis-writer-worker.js.map +1 -0
  42. package/dist/ktx2-basis-writer.js +1 -0
  43. package/dist/ktx2-basis-writer.js.map +1 -0
  44. package/dist/lib/composite-image/image-texture-cube.d.ts +47 -0
  45. package/dist/lib/composite-image/image-texture-cube.d.ts.map +1 -0
  46. package/dist/lib/composite-image/image-texture-cube.js +42 -0
  47. package/dist/lib/composite-image/image-texture-cube.js.map +1 -0
  48. package/dist/lib/composite-image/parse-composite-image.d.ts +43 -0
  49. package/dist/lib/composite-image/parse-composite-image.d.ts.map +1 -0
  50. package/dist/lib/composite-image/parse-composite-image.js +437 -0
  51. package/dist/lib/composite-image/parse-composite-image.js.map +1 -0
  52. package/dist/lib/encoders/encode-ktx.d.ts +1 -1
  53. package/dist/lib/encoders/encode-ktx.d.ts.map +1 -1
  54. package/dist/lib/encoders/encode-ktx.js +1 -0
  55. package/dist/lib/encoders/encode-ktx.js.map +1 -0
  56. package/dist/lib/encoders/encode-ktx2-basis-texture.d.ts +2 -1
  57. package/dist/lib/encoders/encode-ktx2-basis-texture.d.ts.map +1 -1
  58. package/dist/lib/encoders/encode-ktx2-basis-texture.js +3 -1
  59. package/dist/lib/encoders/encode-ktx2-basis-texture.js.map +1 -0
  60. package/dist/lib/encoders/encode-texture.js +1 -0
  61. package/dist/lib/encoders/encode-texture.js.map +1 -0
  62. package/dist/lib/gl-extensions.d.ts +166 -58
  63. package/dist/lib/gl-extensions.d.ts.map +1 -1
  64. package/dist/lib/gl-extensions.js +178 -66
  65. package/dist/lib/gl-extensions.js.map +1 -0
  66. package/dist/lib/gl-types.d.ts +4 -0
  67. package/dist/lib/gl-types.d.ts.map +1 -0
  68. package/dist/lib/gl-types.js +5 -0
  69. package/dist/lib/gl-types.js.map +1 -0
  70. package/dist/lib/parsers/basis-module-loader.d.ts +3 -2
  71. package/dist/lib/parsers/basis-module-loader.d.ts.map +1 -1
  72. package/dist/lib/parsers/basis-module-loader.js +1 -0
  73. package/dist/lib/parsers/basis-module-loader.js.map +1 -0
  74. package/dist/lib/parsers/crunch-module-loader.d.ts +2 -2
  75. package/dist/lib/parsers/crunch-module-loader.d.ts.map +1 -1
  76. package/dist/lib/parsers/crunch-module-loader.js +1 -0
  77. package/dist/lib/parsers/crunch-module-loader.js.map +1 -0
  78. package/dist/lib/parsers/parse-basis.d.ts +34 -2
  79. package/dist/lib/parsers/parse-basis.d.ts.map +1 -1
  80. package/dist/lib/parsers/parse-basis.js +265 -64
  81. package/dist/lib/parsers/parse-basis.js.map +1 -0
  82. package/dist/lib/parsers/parse-compressed-texture.js +1 -0
  83. package/dist/lib/parsers/parse-compressed-texture.js.map +1 -0
  84. package/dist/lib/parsers/parse-crunch.d.ts.map +1 -1
  85. package/dist/lib/parsers/parse-crunch.js +7 -6
  86. package/dist/lib/parsers/parse-crunch.js.map +1 -0
  87. package/dist/lib/parsers/parse-dds.d.ts.map +1 -1
  88. package/dist/lib/parsers/parse-dds.js +11 -11
  89. package/dist/lib/parsers/parse-dds.js.map +1 -0
  90. package/dist/lib/parsers/parse-hdr.d.ts +21 -0
  91. package/dist/lib/parsers/parse-hdr.d.ts.map +1 -0
  92. package/dist/lib/parsers/parse-hdr.js +305 -0
  93. package/dist/lib/parsers/parse-hdr.js.map +1 -0
  94. package/dist/lib/parsers/parse-ktx.d.ts.map +1 -1
  95. package/dist/lib/parsers/parse-ktx.js +11 -3
  96. package/dist/lib/parsers/parse-ktx.js.map +1 -0
  97. package/dist/lib/parsers/parse-npy.js +1 -0
  98. package/dist/lib/parsers/parse-npy.js.map +1 -0
  99. package/dist/lib/parsers/parse-pvr.d.ts.map +1 -1
  100. package/dist/lib/parsers/parse-pvr.js +32 -74
  101. package/dist/lib/parsers/parse-pvr.js.map +1 -0
  102. package/dist/lib/texture-api/async-deep-map.js +1 -0
  103. package/dist/lib/texture-api/async-deep-map.js.map +1 -0
  104. package/dist/lib/texture-api/deep-load.js +1 -0
  105. package/dist/lib/texture-api/deep-load.js.map +1 -0
  106. package/dist/lib/texture-api/generate-url.d.ts.map +1 -1
  107. package/dist/lib/texture-api/generate-url.js +3 -10
  108. package/dist/lib/texture-api/generate-url.js.map +1 -0
  109. package/dist/lib/texture-api/load-image-array.d.ts +6 -3
  110. package/dist/lib/texture-api/load-image-array.d.ts.map +1 -1
  111. package/dist/lib/texture-api/load-image-array.js +6 -3
  112. package/dist/lib/texture-api/load-image-array.js.map +1 -0
  113. package/dist/lib/texture-api/load-image-cube.d.ts +7 -11
  114. package/dist/lib/texture-api/load-image-cube.d.ts.map +1 -1
  115. package/dist/lib/texture-api/load-image-cube.js +9 -20
  116. package/dist/lib/texture-api/load-image-cube.js.map +1 -0
  117. package/dist/lib/texture-api/load-image.d.ts +6 -3
  118. package/dist/lib/texture-api/load-image.d.ts.map +1 -1
  119. package/dist/lib/texture-api/load-image.js +9 -4
  120. package/dist/lib/texture-api/load-image.js.map +1 -0
  121. package/dist/lib/texture-api/texture-api-types.d.ts +13 -0
  122. package/dist/lib/texture-api/texture-api-types.d.ts.map +1 -1
  123. package/dist/lib/texture-api/texture-api-types.js +1 -0
  124. package/dist/lib/texture-api/texture-api-types.js.map +1 -0
  125. package/dist/lib/utils/detect-supported-texture-formats.d.ts +14 -0
  126. package/dist/lib/utils/detect-supported-texture-formats.d.ts.map +1 -0
  127. package/dist/lib/utils/detect-supported-texture-formats.js +197 -0
  128. package/dist/lib/utils/detect-supported-texture-formats.js.map +1 -0
  129. package/dist/lib/utils/extract-mipmap-images.d.ts +6 -2
  130. package/dist/lib/utils/extract-mipmap-images.d.ts.map +1 -1
  131. package/dist/lib/utils/extract-mipmap-images.js +14 -2
  132. package/dist/lib/utils/extract-mipmap-images.js.map +1 -0
  133. package/dist/lib/utils/ktx-format-helper.d.ts +9 -1
  134. package/dist/lib/utils/ktx-format-helper.d.ts.map +1 -1
  135. package/dist/lib/utils/ktx-format-helper.js +77 -109
  136. package/dist/lib/utils/ktx-format-helper.js.map +1 -0
  137. package/dist/lib/utils/texture-format-map.d.ts +10 -0
  138. package/dist/lib/utils/texture-format-map.d.ts.map +1 -0
  139. package/dist/lib/utils/texture-format-map.js +87 -0
  140. package/dist/lib/utils/texture-format-map.js.map +1 -0
  141. package/dist/lib/utils/version.js +2 -1
  142. package/dist/lib/utils/version.js.map +1 -0
  143. package/dist/libs/libs/README.md +9 -0
  144. package/dist/libs/libs/basis_encoder.js +21 -0
  145. package/dist/libs/libs/basis_encoder.wasm +0 -0
  146. package/dist/libs/libs/basis_transcoder.js +22 -0
  147. package/dist/libs/libs/basis_transcoder.wasm +0 -0
  148. package/dist/libs/libs/crunch.js +136 -0
  149. package/dist/npy-loader.d.ts +2 -2
  150. package/dist/npy-loader.js +1 -0
  151. package/dist/npy-loader.js.map +1 -0
  152. package/dist/npy-worker.d.ts +2 -0
  153. package/dist/npy-worker.d.ts.map +1 -0
  154. package/dist/npy-worker.js +5 -2
  155. package/dist/npy-worker.js.map +1 -0
  156. package/dist/radiance-hdr-loader.d.ts +25 -0
  157. package/dist/radiance-hdr-loader.d.ts.map +1 -0
  158. package/dist/radiance-hdr-loader.js +23 -0
  159. package/dist/radiance-hdr-loader.js.map +1 -0
  160. package/dist/texture-array-loader.d.ts +25 -0
  161. package/dist/texture-array-loader.d.ts.map +1 -0
  162. package/dist/texture-array-loader.js +24 -0
  163. package/dist/texture-array-loader.js.map +1 -0
  164. package/dist/texture-cube-array-loader.d.ts +25 -0
  165. package/dist/texture-cube-array-loader.d.ts.map +1 -0
  166. package/dist/texture-cube-array-loader.js +24 -0
  167. package/dist/texture-cube-array-loader.js.map +1 -0
  168. package/dist/texture-cube-loader.d.ts +25 -0
  169. package/dist/texture-cube-loader.d.ts.map +1 -0
  170. package/dist/texture-cube-loader.js +24 -0
  171. package/dist/texture-cube-loader.js.map +1 -0
  172. package/dist/texture-loader.d.ts +25 -0
  173. package/dist/texture-loader.d.ts.map +1 -0
  174. package/dist/texture-loader.js +24 -0
  175. package/dist/texture-loader.js.map +1 -0
  176. package/dist/workers/basis-worker-node.js +1 -0
  177. package/dist/workers/basis-worker-node.js.map +1 -0
  178. package/dist/workers/basis-worker.js +1 -0
  179. package/dist/workers/basis-worker.js.map +1 -0
  180. package/dist/workers/compressed-texture-worker.js +1 -1
  181. package/dist/workers/compressed-texture-worker.js.map +1 -0
  182. package/dist/workers/crunch-worker.d.ts +1 -3
  183. package/dist/workers/crunch-worker.d.ts.map +1 -1
  184. package/dist/workers/crunch-worker.js +1 -0
  185. package/dist/workers/crunch-worker.js.map +1 -0
  186. package/dist/workers/ktx2-basis-writer-worker-node.js +1 -0
  187. package/dist/workers/ktx2-basis-writer-worker-node.js.map +1 -0
  188. package/dist/workers/ktx2-basis-writer-worker.js +1 -0
  189. package/dist/workers/ktx2-basis-writer-worker.js.map +1 -0
  190. package/dist/workers/npy-worker.js +1 -0
  191. package/dist/workers/npy-worker.js.map +1 -0
  192. package/package.json +27 -6
  193. package/src/basis-loader.ts +19 -9
  194. package/src/basis-worker.ts +7 -0
  195. package/src/compressed-texture-loader.ts +3 -7
  196. package/src/compressed-texture-worker.ts +6 -0
  197. package/src/crunch-loader.ts +1 -5
  198. package/src/crunch-worker.ts +6 -0
  199. package/src/index.ts +21 -4
  200. package/src/ktx2-basis-writer-worker.ts +6 -0
  201. package/src/lib/composite-image/image-texture-cube.ts +49 -0
  202. package/src/lib/composite-image/parse-composite-image.ts +699 -0
  203. package/src/lib/encoders/encode-ktx.ts +1 -1
  204. package/src/lib/encoders/encode-ktx2-basis-texture.ts +4 -2
  205. package/src/lib/gl-extensions.ts +188 -81
  206. package/src/lib/gl-types.ts +136 -0
  207. package/src/lib/parsers/basis-module-loader.ts +5 -5
  208. package/src/lib/parsers/crunch-module-loader.ts +4 -4
  209. package/src/lib/parsers/parse-basis.ts +358 -66
  210. package/src/lib/parsers/parse-crunch.ts +11 -8
  211. package/src/lib/parsers/parse-dds.ts +11 -12
  212. package/src/lib/parsers/parse-hdr.ts +426 -0
  213. package/src/lib/parsers/parse-ktx.ts +13 -3
  214. package/src/lib/parsers/parse-pvr.ts +33 -75
  215. package/src/lib/texture-api/generate-url.ts +2 -12
  216. package/src/lib/texture-api/load-image-array.ts +15 -6
  217. package/src/lib/texture-api/load-image-cube.ts +20 -34
  218. package/src/lib/texture-api/load-image.ts +19 -8
  219. package/src/lib/texture-api/texture-api-types.ts +15 -0
  220. package/src/lib/utils/detect-supported-texture-formats.ts +210 -0
  221. package/src/lib/utils/extract-mipmap-images.ts +23 -4
  222. package/src/lib/utils/ktx-format-helper.ts +135 -111
  223. package/src/lib/utils/texture-format-map.ts +162 -0
  224. package/src/npy-worker.ts +6 -0
  225. package/src/radiance-hdr-loader.ts +36 -0
  226. package/src/texture-array-loader.ts +46 -0
  227. package/src/texture-cube-array-loader.ts +49 -0
  228. package/src/texture-cube-loader.ts +46 -0
  229. package/src/texture-loader.ts +49 -0
  230. package/src/workers/compressed-texture-worker.ts +0 -1
  231. package/dist/lib/utils/texture-formats.d.ts +0 -8
  232. package/dist/lib/utils/texture-formats.d.ts.map +0 -1
  233. package/dist/lib/utils/texture-formats.js +0 -50
  234. package/src/lib/utils/texture-formats.ts +0 -59
@@ -0,0 +1,699 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {LoaderContext} from '@loaders.gl/loader-utils';
6
+ import {parseFromContext, path, resolvePath} from '@loaders.gl/loader-utils';
7
+ import type {Texture, TextureFormat, TextureLevel} from '@loaders.gl/schema';
8
+ import {ImageLoader, getImageSize, isImage, type ImageType} from '@loaders.gl/images';
9
+ import {asyncDeepMap} from '../texture-api/async-deep-map';
10
+ import type {TextureLoaderOptions} from '../texture-api/texture-api-types';
11
+ import {
12
+ IMAGE_TEXTURE_CUBE_FACES,
13
+ type ImageCubeTexture,
14
+ type ImageTextureCubeDirectionAlias,
15
+ type ImageTextureCubeFace
16
+ } from './image-texture-cube';
17
+
18
+ export type ImageTextureTemplateSource = {
19
+ mipLevels: number | 'auto';
20
+ template: string;
21
+ };
22
+
23
+ export type ImageTextureSource = string | string[] | ImageTextureTemplateSource;
24
+
25
+ export type ImageTextureManifest = {
26
+ shape: 'image-texture';
27
+ image?: string;
28
+ mipLevels?: number | 'auto';
29
+ template?: string;
30
+ mipmaps?: string[];
31
+ };
32
+
33
+ export type ImageTextureArrayManifest = {
34
+ shape: 'image-texture-array';
35
+ layers: ImageTextureSource[];
36
+ };
37
+
38
+ export type ImageTextureCubeFaces = Partial<
39
+ Record<ImageTextureCubeFace | ImageTextureCubeDirectionAlias, ImageTextureSource>
40
+ >;
41
+
42
+ export type ImageTextureCubeManifest = {
43
+ shape: 'image-texture-cube';
44
+ faces: ImageTextureCubeFaces;
45
+ };
46
+
47
+ export type ImageTextureCubeArrayLayer = {
48
+ faces: ImageTextureCubeFaces;
49
+ };
50
+
51
+ export type ImageTextureCubeArrayManifest = {
52
+ shape: 'image-texture-cube-array';
53
+ layers: ImageTextureCubeArrayLayer[];
54
+ };
55
+
56
+ export type CompositeImageManifest =
57
+ | ImageTextureManifest
58
+ | ImageTextureArrayManifest
59
+ | ImageTextureCubeManifest
60
+ | ImageTextureCubeArrayManifest;
61
+
62
+ export type CompositeImageUrlTree =
63
+ | ImageTextureSource
64
+ | ImageTextureSource[]
65
+ | ImageCubeTexture
66
+ | ImageCubeTexture[];
67
+
68
+ export async function parseCompositeImageManifest(
69
+ text: string,
70
+ expectedShape: CompositeImageManifest['shape'],
71
+ options: TextureLoaderOptions = {},
72
+ context?: LoaderContext
73
+ ): Promise<any> {
74
+ const manifest = parseCompositeImageManifestJSON(text);
75
+ if (manifest.shape !== expectedShape) {
76
+ throw new Error(`Expected ${expectedShape} manifest, got ${manifest.shape}`);
77
+ }
78
+ return await loadCompositeImageManifest(manifest, options, context);
79
+ }
80
+
81
+ export function testCompositeImageManifestShape(
82
+ text: string,
83
+ shape: CompositeImageManifest['shape']
84
+ ): boolean {
85
+ try {
86
+ return parseCompositeImageManifestJSON(text).shape === shape;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ export async function loadCompositeImageManifest(
93
+ manifest: CompositeImageManifest,
94
+ options: TextureLoaderOptions = {},
95
+ context?: LoaderContext
96
+ ): Promise<Texture> {
97
+ const normalizedOptions = normalizeCompositeImageManifestOptions(options);
98
+ const urlTree = await getCompositeImageUrlTree(manifest, normalizedOptions, context);
99
+ const imageData = await loadCompositeImageUrlTree(urlTree, normalizedOptions, context);
100
+ return convertCompositeImageToTexture(manifest.shape, imageData);
101
+ }
102
+
103
+ export async function loadCompositeImageUrlTree(
104
+ urlTree: CompositeImageUrlTree,
105
+ options: TextureLoaderOptions = {},
106
+ context?: LoaderContext
107
+ ): Promise<any> {
108
+ const normalizedOptions = normalizeCompositeImageOptions(options);
109
+ return await asyncDeepMap(
110
+ urlTree,
111
+ async (url: string) => await loadCompositeImageMember(url, normalizedOptions, context)
112
+ );
113
+ }
114
+
115
+ export async function loadCompositeImageMember(
116
+ url: string,
117
+ options: TextureLoaderOptions = {},
118
+ context?: LoaderContext
119
+ ): Promise<any> {
120
+ const resolvedUrl = resolveCompositeImageUrl(url, options, context);
121
+ const fetch = getCompositeImageFetch(options, context);
122
+ const response = await fetch(resolvedUrl);
123
+ const subloaderOptions = getCompositeImageSubloaderOptions(options);
124
+ if (context) {
125
+ const childContext = getCompositeImageMemberContext(resolvedUrl, response, context);
126
+ return await parseFromContext(
127
+ response as any,
128
+ [ImageLoader],
129
+ subloaderOptions as any,
130
+ childContext
131
+ );
132
+ }
133
+
134
+ const arrayBuffer = await response.arrayBuffer();
135
+ return await ImageLoader.parse(arrayBuffer, subloaderOptions as any);
136
+ }
137
+
138
+ export async function getCompositeImageUrlTree(
139
+ manifest: CompositeImageManifest,
140
+ options: TextureLoaderOptions = {},
141
+ context?: LoaderContext
142
+ ): Promise<CompositeImageUrlTree> {
143
+ switch (manifest.shape) {
144
+ case 'image-texture':
145
+ return await getImageTextureSource(manifest, options, context);
146
+
147
+ case 'image-texture-array':
148
+ if (!Array.isArray(manifest.layers) || manifest.layers.length === 0) {
149
+ throw new Error('image-texture-array manifest must define one or more layers');
150
+ }
151
+ return await Promise.all(
152
+ manifest.layers.map(
153
+ async (layer, index) =>
154
+ await getNormalizedImageTextureSource(layer, options, context, {index})
155
+ )
156
+ );
157
+
158
+ case 'image-texture-cube':
159
+ return await getImageTextureCubeUrls(manifest, options, context);
160
+
161
+ case 'image-texture-cube-array':
162
+ if (!Array.isArray(manifest.layers) || manifest.layers.length === 0) {
163
+ throw new Error('image-texture-cube-array manifest must define one or more layers');
164
+ }
165
+ return await Promise.all(
166
+ manifest.layers.map(
167
+ async (layer, index) => await getImageTextureCubeUrls(layer, options, context, {index})
168
+ )
169
+ );
170
+
171
+ default:
172
+ throw new Error('Unsupported composite image manifest');
173
+ }
174
+ }
175
+
176
+ export function normalizeCompositeImageOptions(
177
+ options: TextureLoaderOptions = {}
178
+ ): TextureLoaderOptions {
179
+ if (options.core?.baseUrl) {
180
+ return options;
181
+ }
182
+
183
+ const fallbackBaseUrl = options.baseUrl;
184
+ if (!fallbackBaseUrl) {
185
+ return options;
186
+ }
187
+
188
+ return {
189
+ ...options,
190
+ core: {
191
+ ...options.core,
192
+ baseUrl: fallbackBaseUrl
193
+ }
194
+ };
195
+ }
196
+
197
+ export function resolveCompositeImageUrl(
198
+ url: string,
199
+ options: TextureLoaderOptions = {},
200
+ context?: LoaderContext
201
+ ): string {
202
+ const resolvedUrl = resolvePath(url);
203
+ if (isAbsoluteCompositeImageUrl(url)) {
204
+ return resolvedUrl;
205
+ }
206
+
207
+ const baseUrl = getCompositeImageBaseUrl(options, context);
208
+ if (!baseUrl) {
209
+ if (resolvedUrl !== url || url.startsWith('@')) {
210
+ return resolvedUrl;
211
+ }
212
+ throw new Error(`Unable to resolve relative image URL ${url} without a base URL`);
213
+ }
214
+
215
+ return resolvePath(joinCompositeImageUrl(baseUrl, url));
216
+ }
217
+
218
+ function parseCompositeImageManifestJSON(text: string): CompositeImageManifest {
219
+ const manifest = JSON.parse(text) as CompositeImageManifest;
220
+ if (!manifest?.shape) {
221
+ throw new Error('Composite image manifest must contain a shape field');
222
+ }
223
+ return manifest;
224
+ }
225
+
226
+ async function getImageTextureSource(
227
+ manifest: ImageTextureManifest,
228
+ options: TextureLoaderOptions,
229
+ context?: LoaderContext
230
+ ): Promise<ImageTextureSource> {
231
+ if ((manifest.image || manifest.mipmaps) && manifest.template) {
232
+ throw new Error('image-texture manifest must define image, mipmaps, or template source');
233
+ }
234
+ if (manifest.image && manifest.mipmaps) {
235
+ throw new Error('image-texture manifest must define image, mipmaps, or template source');
236
+ }
237
+ if (manifest.image) {
238
+ return manifest.image;
239
+ }
240
+ if (manifest.mipmaps?.length) {
241
+ return manifest.mipmaps;
242
+ }
243
+ if (manifest.template) {
244
+ return await expandImageTextureSource(
245
+ {mipLevels: manifest.mipLevels ?? 'auto', template: manifest.template},
246
+ options,
247
+ context,
248
+ {}
249
+ );
250
+ }
251
+ throw new Error('image-texture manifest must define image, mipmaps, or template source');
252
+ }
253
+
254
+ async function getImageTextureCubeUrls(
255
+ manifest: Pick<ImageTextureCubeManifest, 'faces'>,
256
+ options: TextureLoaderOptions,
257
+ context?: LoaderContext,
258
+ templateOptions: TemplateOptions = {}
259
+ ): Promise<ImageCubeTexture> {
260
+ const urls: ImageCubeTexture = {};
261
+
262
+ for (const {face, name, direction, axis, sign} of IMAGE_TEXTURE_CUBE_FACES) {
263
+ const source = manifest.faces?.[name] || manifest.faces?.[direction];
264
+ if (!source) {
265
+ throw new Error(`image-texture-cube manifest is missing ${name} face`);
266
+ }
267
+ urls[face] = await getNormalizedImageTextureSource(source, options, context, {
268
+ ...templateOptions,
269
+ face: name,
270
+ direction,
271
+ axis,
272
+ sign
273
+ });
274
+ }
275
+
276
+ return urls;
277
+ }
278
+
279
+ async function getNormalizedImageTextureSource(
280
+ source: ImageTextureSource,
281
+ options: TextureLoaderOptions,
282
+ context: LoaderContext | undefined,
283
+ templateOptions: TemplateOptions
284
+ ): Promise<ImageTextureSource> {
285
+ if (typeof source === 'string') {
286
+ return source;
287
+ }
288
+ if (Array.isArray(source) && source.length > 0) {
289
+ return source;
290
+ }
291
+ if (isImageTextureTemplateSource(source)) {
292
+ return await expandImageTextureSource(source, options, context, templateOptions);
293
+ }
294
+ throw new Error('Composite image source entries must be strings or non-empty mip arrays');
295
+ }
296
+
297
+ async function expandImageTextureSource(
298
+ source: ImageTextureTemplateSource,
299
+ options: TextureLoaderOptions,
300
+ context: LoaderContext | undefined,
301
+ templateOptions: TemplateOptions
302
+ ): Promise<string[]> {
303
+ const mipLevels =
304
+ source.mipLevels === 'auto'
305
+ ? await getAutoMipLevels(source.template, options, context, templateOptions)
306
+ : source.mipLevels;
307
+
308
+ if (!Number.isFinite(mipLevels) || mipLevels <= 0) {
309
+ throw new Error(`Invalid mipLevels value ${source.mipLevels}`);
310
+ }
311
+
312
+ const urls: string[] = [];
313
+ for (let lod = 0; lod < mipLevels; lod++) {
314
+ urls.push(expandTemplate(source.template, {...templateOptions, lod}));
315
+ }
316
+ return urls;
317
+ }
318
+
319
+ async function getAutoMipLevels(
320
+ template: string,
321
+ options: TextureLoaderOptions,
322
+ context: LoaderContext | undefined,
323
+ templateOptions: TemplateOptions
324
+ ): Promise<number> {
325
+ if (!template.includes('{lod}')) {
326
+ throw new Error('Template sources with mipLevels: auto must include a {lod} placeholder');
327
+ }
328
+
329
+ const level0Url = expandTemplate(template, {...templateOptions, lod: 0});
330
+ const image = await loadCompositeImageMember(
331
+ level0Url,
332
+ normalizeCompositeImageOptions(options),
333
+ context
334
+ );
335
+ const {width, height} = getImageSize(image);
336
+ return 1 + Math.floor(Math.log2(Math.max(width, height)));
337
+ }
338
+
339
+ type TemplateOptions = {
340
+ lod?: number;
341
+ index?: number;
342
+ face?: string;
343
+ direction?: string;
344
+ axis?: string;
345
+ sign?: string;
346
+ };
347
+
348
+ function expandTemplate(template: string, templateOptions: TemplateOptions): string {
349
+ let expanded = '';
350
+
351
+ for (let index = 0; index < template.length; index++) {
352
+ const character = template[index];
353
+
354
+ if (character === '\\') {
355
+ const nextCharacter = template[index + 1];
356
+ if (nextCharacter === '{' || nextCharacter === '}' || nextCharacter === '\\') {
357
+ expanded += nextCharacter;
358
+ index++;
359
+ continue;
360
+ }
361
+ throw new Error(`Invalid escape sequence \\${nextCharacter || ''} in template ${template}`);
362
+ }
363
+
364
+ if (character === '}') {
365
+ throw new Error(`Unexpected } in template ${template}`);
366
+ }
367
+
368
+ if (character !== '{') {
369
+ expanded += character;
370
+ continue;
371
+ }
372
+
373
+ const closingBraceIndex = findClosingBraceIndex(template, index + 1);
374
+ if (closingBraceIndex < 0) {
375
+ throw new Error(`Unterminated placeholder in template ${template}`);
376
+ }
377
+
378
+ const placeholder = template.slice(index + 1, closingBraceIndex);
379
+ if (!/^[a-z][a-zA-Z0-9]*$/.test(placeholder)) {
380
+ throw new Error(`Invalid placeholder {${placeholder}} in template ${template}`);
381
+ }
382
+
383
+ const value = getTemplateValue(placeholder, templateOptions);
384
+ if (value === undefined) {
385
+ throw new Error(
386
+ `Template ${template} uses unsupported placeholder {${placeholder}} for this source`
387
+ );
388
+ }
389
+
390
+ expanded += String(value);
391
+ index = closingBraceIndex;
392
+ }
393
+
394
+ return expanded;
395
+ }
396
+
397
+ function findClosingBraceIndex(template: string, startIndex: number): number {
398
+ for (let index = startIndex; index < template.length; index++) {
399
+ const character = template[index];
400
+ if (character === '\\') {
401
+ index++;
402
+ continue;
403
+ }
404
+ if (character === '{') {
405
+ throw new Error(`Nested placeholders are not supported in template ${template}`);
406
+ }
407
+ if (character === '}') {
408
+ return index;
409
+ }
410
+ }
411
+ return -1;
412
+ }
413
+
414
+ function getTemplateValue(
415
+ placeholder: string,
416
+ templateOptions: TemplateOptions
417
+ ): string | number | undefined {
418
+ switch (placeholder) {
419
+ case 'lod':
420
+ return templateOptions.lod;
421
+ case 'index':
422
+ return templateOptions.index;
423
+ case 'face':
424
+ return templateOptions.face;
425
+ case 'direction':
426
+ return templateOptions.direction;
427
+ case 'axis':
428
+ return templateOptions.axis;
429
+ case 'sign':
430
+ return templateOptions.sign;
431
+ default:
432
+ return undefined;
433
+ }
434
+ }
435
+
436
+ function isImageTextureTemplateSource(
437
+ source: ImageTextureSource
438
+ ): source is ImageTextureTemplateSource {
439
+ return typeof source === 'object' && source !== null && !Array.isArray(source);
440
+ }
441
+
442
+ function getCompositeImageBaseUrl(
443
+ options: TextureLoaderOptions,
444
+ context?: LoaderContext
445
+ ): string | null {
446
+ if (context?.baseUrl) {
447
+ return context.baseUrl;
448
+ }
449
+
450
+ if (options.baseUrl) {
451
+ return stripTrailingSlash(options.baseUrl);
452
+ }
453
+
454
+ if (options.core?.baseUrl) {
455
+ return getSourceUrlDirectory(options.core.baseUrl);
456
+ }
457
+
458
+ return null;
459
+ }
460
+
461
+ function stripTrailingSlash(baseUrl: string): string {
462
+ if (baseUrl.endsWith('/')) {
463
+ return baseUrl.slice(0, -1);
464
+ }
465
+
466
+ return baseUrl;
467
+ }
468
+
469
+ function getSourceUrlDirectory(baseUrl: string): string {
470
+ return stripTrailingSlash(path.dirname(baseUrl));
471
+ }
472
+
473
+ function joinCompositeImageUrl(baseUrl: string, url: string): string {
474
+ if (isRequestLikeUrl(baseUrl)) {
475
+ return new URL(url, `${stripTrailingSlash(baseUrl)}/`).toString();
476
+ }
477
+
478
+ const normalizedBaseUrl = baseUrl.startsWith('/') ? baseUrl : `/${baseUrl}`;
479
+ const normalizedUrl = path.resolve(normalizedBaseUrl, url);
480
+ return baseUrl.startsWith('/') ? normalizedUrl : normalizedUrl.slice(1);
481
+ }
482
+
483
+ function isRequestLikeUrl(url: string): boolean {
484
+ return (
485
+ url.startsWith('http:') ||
486
+ url.startsWith('https:') ||
487
+ url.startsWith('file:') ||
488
+ url.startsWith('blob:')
489
+ );
490
+ }
491
+
492
+ function getCompositeImageFetch(
493
+ options: TextureLoaderOptions,
494
+ context?: LoaderContext
495
+ ): typeof fetch {
496
+ const fetchOption = options.fetch ?? options.core?.fetch;
497
+
498
+ if (context?.fetch) {
499
+ return context.fetch as typeof fetch;
500
+ }
501
+
502
+ if (typeof fetchOption === 'function') {
503
+ return fetchOption as typeof fetch;
504
+ }
505
+
506
+ if (fetchOption && typeof fetchOption === 'object') {
507
+ return (url) => fetch(url, fetchOption);
508
+ }
509
+
510
+ return fetch;
511
+ }
512
+
513
+ function getCompositeImageSubloaderOptions(options: TextureLoaderOptions): TextureLoaderOptions {
514
+ const core = options.core;
515
+ const rest = {...options};
516
+ delete rest.baseUrl;
517
+ if (!core?.baseUrl) {
518
+ return rest;
519
+ }
520
+
521
+ const restCore = {...core};
522
+ delete restCore.baseUrl;
523
+ return {
524
+ ...rest,
525
+ core: restCore
526
+ };
527
+ }
528
+
529
+ function normalizeCompositeImageManifestOptions(
530
+ options: TextureLoaderOptions
531
+ ): TextureLoaderOptions {
532
+ if (options.image?.type || typeof ImageBitmap === 'undefined') {
533
+ return options;
534
+ }
535
+
536
+ return {
537
+ ...options,
538
+ image: {
539
+ ...options.image,
540
+ type: 'imagebitmap'
541
+ }
542
+ };
543
+ }
544
+
545
+ function getCompositeImageMemberContext(
546
+ resolvedUrl: string,
547
+ response: Response,
548
+ context: LoaderContext
549
+ ): LoaderContext {
550
+ const url = response.url || resolvedUrl;
551
+ const [urlWithoutQueryString, queryString = ''] = url.split('?');
552
+
553
+ return {
554
+ ...context,
555
+ url,
556
+ response,
557
+ filename: path.filename(urlWithoutQueryString),
558
+ baseUrl: path.dirname(urlWithoutQueryString),
559
+ queryString
560
+ };
561
+ }
562
+
563
+ function convertCompositeImageToTexture(
564
+ shape: CompositeImageManifest['shape'],
565
+ imageData: any
566
+ ): Texture {
567
+ switch (shape) {
568
+ case 'image-texture': {
569
+ const data = normalizeCompositeImageMember(imageData);
570
+ return {
571
+ shape: 'texture',
572
+ type: '2d',
573
+ format: getCompositeTextureFormat(data),
574
+ data
575
+ };
576
+ }
577
+
578
+ case 'image-texture-array': {
579
+ const data = imageData.map((layer) => normalizeCompositeImageMember(layer));
580
+ return {
581
+ shape: 'texture',
582
+ type: '2d-array',
583
+ format: getCompositeTextureFormat(data[0]),
584
+ data
585
+ };
586
+ }
587
+
588
+ case 'image-texture-cube': {
589
+ const data = IMAGE_TEXTURE_CUBE_FACES.map(({face}) =>
590
+ normalizeCompositeImageMember(imageData[face])
591
+ );
592
+ return {
593
+ shape: 'texture',
594
+ type: 'cube',
595
+ format: getCompositeTextureFormat(data[0]),
596
+ data
597
+ };
598
+ }
599
+
600
+ case 'image-texture-cube-array': {
601
+ const data = imageData.map((layer) =>
602
+ IMAGE_TEXTURE_CUBE_FACES.map(({face}) => normalizeCompositeImageMember(layer[face]))
603
+ );
604
+ return {
605
+ shape: 'texture',
606
+ type: 'cube-array',
607
+ format: getCompositeTextureFormat(data[0][0]),
608
+ data
609
+ };
610
+ }
611
+
612
+ default:
613
+ throw new Error(`Unsupported composite image shape ${shape}`);
614
+ }
615
+ }
616
+
617
+ function normalizeCompositeImageMember(imageData: any): TextureLevel[] {
618
+ if (Array.isArray(imageData)) {
619
+ if (imageData.length === 0) {
620
+ throw new Error('Composite image members must not be empty');
621
+ }
622
+
623
+ if (imageData.every(isTextureLevel)) {
624
+ return imageData;
625
+ }
626
+
627
+ if (imageData.every(isImage)) {
628
+ return imageData.map((image) => getTextureLevelFromImage(image));
629
+ }
630
+
631
+ if (imageData.every((entry) => Array.isArray(entry) && entry.every(isTextureLevel))) {
632
+ if (imageData.length !== 1) {
633
+ throw new Error('Composite image members must resolve to a single image or mip chain');
634
+ }
635
+ return imageData[0];
636
+ }
637
+ }
638
+
639
+ if (isTexture(imageData)) {
640
+ if (imageData.type !== '2d') {
641
+ throw new Error(`Composite image members must resolve to 2d textures, got ${imageData.type}`);
642
+ }
643
+ return imageData.data;
644
+ }
645
+
646
+ if (isTextureLevel(imageData)) {
647
+ return [imageData];
648
+ }
649
+
650
+ if (isImage(imageData)) {
651
+ return [getTextureLevelFromImage(imageData)];
652
+ }
653
+
654
+ throw new Error('Composite image members must resolve to an image, mip chain, or texture');
655
+ }
656
+
657
+ function getTextureLevelFromImage(image: ImageType): TextureLevel {
658
+ const {width, height} = getImageSize(image);
659
+ return {
660
+ shape: 'texture-level',
661
+ compressed: false,
662
+ width,
663
+ height,
664
+ imageBitmap:
665
+ typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ? image : undefined,
666
+ data: new Uint8Array(0),
667
+ textureFormat: 'rgba8unorm'
668
+ };
669
+ }
670
+
671
+ function getCompositeTextureFormat(textureLevels: TextureLevel[]): TextureFormat {
672
+ return textureLevels[0]?.textureFormat || 'rgba8unorm';
673
+ }
674
+
675
+ function isTextureLevel(textureLevel: unknown): textureLevel is TextureLevel {
676
+ return Boolean(
677
+ textureLevel &&
678
+ typeof textureLevel === 'object' &&
679
+ 'shape' in textureLevel &&
680
+ textureLevel.shape === 'texture-level'
681
+ );
682
+ }
683
+
684
+ function isTexture(texture: unknown): texture is Texture {
685
+ return Boolean(
686
+ texture && typeof texture === 'object' && 'shape' in texture && texture.shape === 'texture'
687
+ );
688
+ }
689
+
690
+ function isAbsoluteCompositeImageUrl(url: string): boolean {
691
+ return (
692
+ url.startsWith('data:') ||
693
+ url.startsWith('blob:') ||
694
+ url.startsWith('file:') ||
695
+ url.startsWith('http:') ||
696
+ url.startsWith('https:') ||
697
+ url.startsWith('/')
698
+ );
699
+ }
@@ -4,7 +4,7 @@
4
4
 
5
5
  import {read} from 'ktx-parse';
6
6
 
7
- export function encodeKTX(texture) {
7
+ export function encodeKTX(texture: Uint8Array) {
8
8
  const ktx = read(texture);
9
9
  // post process
10
10
  return ktx;