@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.
- package/README.md +197 -1
- package/dist/basis-loader.d.ts +15 -10
- package/dist/basis-loader.d.ts.map +1 -1
- package/dist/basis-loader.js +1 -1
- package/dist/basis-loader.js.map +1 -0
- package/dist/basis-worker-node.js +932 -10040
- package/dist/basis-worker.d.ts +2 -0
- package/dist/basis-worker.d.ts.map +1 -0
- package/dist/basis-worker.js +337 -151
- package/dist/basis-worker.js.map +1 -0
- package/dist/compressed-texture-loader.d.ts +2 -5
- package/dist/compressed-texture-loader.d.ts.map +1 -1
- package/dist/compressed-texture-loader.js +2 -3
- package/dist/compressed-texture-loader.js.map +1 -0
- package/dist/compressed-texture-worker.d.ts +2 -0
- package/dist/compressed-texture-worker.d.ts.map +1 -0
- package/dist/compressed-texture-worker.js +1148 -365
- package/dist/compressed-texture-worker.js.map +1 -0
- package/dist/compressed-texture-writer.js +1 -0
- package/dist/compressed-texture-writer.js.map +1 -0
- package/dist/crunch-loader.d.ts +1 -5
- package/dist/crunch-loader.d.ts.map +1 -1
- package/dist/crunch-loader.js +2 -3
- package/dist/crunch-loader.js.map +1 -0
- package/dist/crunch-worker.d.ts +2 -0
- package/dist/crunch-worker.d.ts.map +1 -0
- package/dist/crunch-worker.js +204 -92
- package/dist/crunch-worker.js.map +1 -0
- package/dist/dist.dev.js +2687 -677
- package/dist/dist.min.js +1 -2
- package/dist/index.cjs +1644 -428
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -0
- package/dist/ktx2-basis-writer-worker-node.js +574 -9832
- package/dist/ktx2-basis-writer-worker.d.ts +2 -0
- package/dist/ktx2-basis-writer-worker.d.ts.map +1 -0
- package/dist/ktx2-basis-writer-worker.js +45 -7
- package/dist/ktx2-basis-writer-worker.js.map +1 -0
- package/dist/ktx2-basis-writer.js +1 -0
- package/dist/ktx2-basis-writer.js.map +1 -0
- package/dist/lib/composite-image/image-texture-cube.d.ts +47 -0
- package/dist/lib/composite-image/image-texture-cube.d.ts.map +1 -0
- package/dist/lib/composite-image/image-texture-cube.js +42 -0
- package/dist/lib/composite-image/image-texture-cube.js.map +1 -0
- package/dist/lib/composite-image/parse-composite-image.d.ts +43 -0
- package/dist/lib/composite-image/parse-composite-image.d.ts.map +1 -0
- package/dist/lib/composite-image/parse-composite-image.js +437 -0
- package/dist/lib/composite-image/parse-composite-image.js.map +1 -0
- package/dist/lib/encoders/encode-ktx.d.ts +1 -1
- package/dist/lib/encoders/encode-ktx.d.ts.map +1 -1
- package/dist/lib/encoders/encode-ktx.js +1 -0
- package/dist/lib/encoders/encode-ktx.js.map +1 -0
- package/dist/lib/encoders/encode-ktx2-basis-texture.d.ts +2 -1
- package/dist/lib/encoders/encode-ktx2-basis-texture.d.ts.map +1 -1
- package/dist/lib/encoders/encode-ktx2-basis-texture.js +3 -1
- package/dist/lib/encoders/encode-ktx2-basis-texture.js.map +1 -0
- package/dist/lib/encoders/encode-texture.js +1 -0
- package/dist/lib/encoders/encode-texture.js.map +1 -0
- package/dist/lib/gl-extensions.d.ts +166 -58
- package/dist/lib/gl-extensions.d.ts.map +1 -1
- package/dist/lib/gl-extensions.js +178 -66
- package/dist/lib/gl-extensions.js.map +1 -0
- package/dist/lib/gl-types.d.ts +4 -0
- package/dist/lib/gl-types.d.ts.map +1 -0
- package/dist/lib/gl-types.js +5 -0
- package/dist/lib/gl-types.js.map +1 -0
- package/dist/lib/parsers/basis-module-loader.d.ts +3 -2
- package/dist/lib/parsers/basis-module-loader.d.ts.map +1 -1
- package/dist/lib/parsers/basis-module-loader.js +1 -0
- package/dist/lib/parsers/basis-module-loader.js.map +1 -0
- package/dist/lib/parsers/crunch-module-loader.d.ts +2 -2
- package/dist/lib/parsers/crunch-module-loader.d.ts.map +1 -1
- package/dist/lib/parsers/crunch-module-loader.js +1 -0
- package/dist/lib/parsers/crunch-module-loader.js.map +1 -0
- package/dist/lib/parsers/parse-basis.d.ts +34 -2
- package/dist/lib/parsers/parse-basis.d.ts.map +1 -1
- package/dist/lib/parsers/parse-basis.js +265 -64
- package/dist/lib/parsers/parse-basis.js.map +1 -0
- package/dist/lib/parsers/parse-compressed-texture.js +1 -0
- package/dist/lib/parsers/parse-compressed-texture.js.map +1 -0
- package/dist/lib/parsers/parse-crunch.d.ts.map +1 -1
- package/dist/lib/parsers/parse-crunch.js +7 -6
- package/dist/lib/parsers/parse-crunch.js.map +1 -0
- package/dist/lib/parsers/parse-dds.d.ts.map +1 -1
- package/dist/lib/parsers/parse-dds.js +11 -11
- package/dist/lib/parsers/parse-dds.js.map +1 -0
- package/dist/lib/parsers/parse-hdr.d.ts +21 -0
- package/dist/lib/parsers/parse-hdr.d.ts.map +1 -0
- package/dist/lib/parsers/parse-hdr.js +305 -0
- package/dist/lib/parsers/parse-hdr.js.map +1 -0
- package/dist/lib/parsers/parse-ktx.d.ts.map +1 -1
- package/dist/lib/parsers/parse-ktx.js +11 -3
- package/dist/lib/parsers/parse-ktx.js.map +1 -0
- package/dist/lib/parsers/parse-npy.js +1 -0
- package/dist/lib/parsers/parse-npy.js.map +1 -0
- package/dist/lib/parsers/parse-pvr.d.ts.map +1 -1
- package/dist/lib/parsers/parse-pvr.js +32 -74
- package/dist/lib/parsers/parse-pvr.js.map +1 -0
- package/dist/lib/texture-api/async-deep-map.js +1 -0
- package/dist/lib/texture-api/async-deep-map.js.map +1 -0
- package/dist/lib/texture-api/deep-load.js +1 -0
- package/dist/lib/texture-api/deep-load.js.map +1 -0
- package/dist/lib/texture-api/generate-url.d.ts.map +1 -1
- package/dist/lib/texture-api/generate-url.js +3 -10
- package/dist/lib/texture-api/generate-url.js.map +1 -0
- package/dist/lib/texture-api/load-image-array.d.ts +6 -3
- package/dist/lib/texture-api/load-image-array.d.ts.map +1 -1
- package/dist/lib/texture-api/load-image-array.js +6 -3
- package/dist/lib/texture-api/load-image-array.js.map +1 -0
- package/dist/lib/texture-api/load-image-cube.d.ts +7 -11
- package/dist/lib/texture-api/load-image-cube.d.ts.map +1 -1
- package/dist/lib/texture-api/load-image-cube.js +9 -20
- package/dist/lib/texture-api/load-image-cube.js.map +1 -0
- package/dist/lib/texture-api/load-image.d.ts +6 -3
- package/dist/lib/texture-api/load-image.d.ts.map +1 -1
- package/dist/lib/texture-api/load-image.js +9 -4
- package/dist/lib/texture-api/load-image.js.map +1 -0
- package/dist/lib/texture-api/texture-api-types.d.ts +13 -0
- package/dist/lib/texture-api/texture-api-types.d.ts.map +1 -1
- package/dist/lib/texture-api/texture-api-types.js +1 -0
- package/dist/lib/texture-api/texture-api-types.js.map +1 -0
- package/dist/lib/utils/detect-supported-texture-formats.d.ts +14 -0
- package/dist/lib/utils/detect-supported-texture-formats.d.ts.map +1 -0
- package/dist/lib/utils/detect-supported-texture-formats.js +197 -0
- package/dist/lib/utils/detect-supported-texture-formats.js.map +1 -0
- package/dist/lib/utils/extract-mipmap-images.d.ts +6 -2
- package/dist/lib/utils/extract-mipmap-images.d.ts.map +1 -1
- package/dist/lib/utils/extract-mipmap-images.js +14 -2
- package/dist/lib/utils/extract-mipmap-images.js.map +1 -0
- package/dist/lib/utils/ktx-format-helper.d.ts +9 -1
- package/dist/lib/utils/ktx-format-helper.d.ts.map +1 -1
- package/dist/lib/utils/ktx-format-helper.js +77 -109
- package/dist/lib/utils/ktx-format-helper.js.map +1 -0
- package/dist/lib/utils/texture-format-map.d.ts +10 -0
- package/dist/lib/utils/texture-format-map.d.ts.map +1 -0
- package/dist/lib/utils/texture-format-map.js +87 -0
- package/dist/lib/utils/texture-format-map.js.map +1 -0
- package/dist/lib/utils/version.js +2 -1
- package/dist/lib/utils/version.js.map +1 -0
- package/dist/libs/libs/README.md +9 -0
- package/dist/libs/libs/basis_encoder.js +21 -0
- package/dist/libs/libs/basis_encoder.wasm +0 -0
- package/dist/libs/libs/basis_transcoder.js +22 -0
- package/dist/libs/libs/basis_transcoder.wasm +0 -0
- package/dist/libs/libs/crunch.js +136 -0
- package/dist/npy-loader.d.ts +2 -2
- package/dist/npy-loader.js +1 -0
- package/dist/npy-loader.js.map +1 -0
- package/dist/npy-worker.d.ts +2 -0
- package/dist/npy-worker.d.ts.map +1 -0
- package/dist/npy-worker.js +5 -2
- package/dist/npy-worker.js.map +1 -0
- package/dist/radiance-hdr-loader.d.ts +25 -0
- package/dist/radiance-hdr-loader.d.ts.map +1 -0
- package/dist/radiance-hdr-loader.js +23 -0
- package/dist/radiance-hdr-loader.js.map +1 -0
- package/dist/texture-array-loader.d.ts +25 -0
- package/dist/texture-array-loader.d.ts.map +1 -0
- package/dist/texture-array-loader.js +24 -0
- package/dist/texture-array-loader.js.map +1 -0
- package/dist/texture-cube-array-loader.d.ts +25 -0
- package/dist/texture-cube-array-loader.d.ts.map +1 -0
- package/dist/texture-cube-array-loader.js +24 -0
- package/dist/texture-cube-array-loader.js.map +1 -0
- package/dist/texture-cube-loader.d.ts +25 -0
- package/dist/texture-cube-loader.d.ts.map +1 -0
- package/dist/texture-cube-loader.js +24 -0
- package/dist/texture-cube-loader.js.map +1 -0
- package/dist/texture-loader.d.ts +25 -0
- package/dist/texture-loader.d.ts.map +1 -0
- package/dist/texture-loader.js +24 -0
- package/dist/texture-loader.js.map +1 -0
- package/dist/workers/basis-worker-node.js +1 -0
- package/dist/workers/basis-worker-node.js.map +1 -0
- package/dist/workers/basis-worker.js +1 -0
- package/dist/workers/basis-worker.js.map +1 -0
- package/dist/workers/compressed-texture-worker.js +1 -1
- package/dist/workers/compressed-texture-worker.js.map +1 -0
- package/dist/workers/crunch-worker.d.ts +1 -3
- package/dist/workers/crunch-worker.d.ts.map +1 -1
- package/dist/workers/crunch-worker.js +1 -0
- package/dist/workers/crunch-worker.js.map +1 -0
- package/dist/workers/ktx2-basis-writer-worker-node.js +1 -0
- package/dist/workers/ktx2-basis-writer-worker-node.js.map +1 -0
- package/dist/workers/ktx2-basis-writer-worker.js +1 -0
- package/dist/workers/ktx2-basis-writer-worker.js.map +1 -0
- package/dist/workers/npy-worker.js +1 -0
- package/dist/workers/npy-worker.js.map +1 -0
- package/package.json +27 -6
- package/src/basis-loader.ts +19 -9
- package/src/basis-worker.ts +7 -0
- package/src/compressed-texture-loader.ts +3 -7
- package/src/compressed-texture-worker.ts +6 -0
- package/src/crunch-loader.ts +1 -5
- package/src/crunch-worker.ts +6 -0
- package/src/index.ts +21 -4
- package/src/ktx2-basis-writer-worker.ts +6 -0
- package/src/lib/composite-image/image-texture-cube.ts +49 -0
- package/src/lib/composite-image/parse-composite-image.ts +699 -0
- package/src/lib/encoders/encode-ktx.ts +1 -1
- package/src/lib/encoders/encode-ktx2-basis-texture.ts +4 -2
- package/src/lib/gl-extensions.ts +188 -81
- package/src/lib/gl-types.ts +136 -0
- package/src/lib/parsers/basis-module-loader.ts +5 -5
- package/src/lib/parsers/crunch-module-loader.ts +4 -4
- package/src/lib/parsers/parse-basis.ts +358 -66
- package/src/lib/parsers/parse-crunch.ts +11 -8
- package/src/lib/parsers/parse-dds.ts +11 -12
- package/src/lib/parsers/parse-hdr.ts +426 -0
- package/src/lib/parsers/parse-ktx.ts +13 -3
- package/src/lib/parsers/parse-pvr.ts +33 -75
- package/src/lib/texture-api/generate-url.ts +2 -12
- package/src/lib/texture-api/load-image-array.ts +15 -6
- package/src/lib/texture-api/load-image-cube.ts +20 -34
- package/src/lib/texture-api/load-image.ts +19 -8
- package/src/lib/texture-api/texture-api-types.ts +15 -0
- package/src/lib/utils/detect-supported-texture-formats.ts +210 -0
- package/src/lib/utils/extract-mipmap-images.ts +23 -4
- package/src/lib/utils/ktx-format-helper.ts +135 -111
- package/src/lib/utils/texture-format-map.ts +162 -0
- package/src/npy-worker.ts +6 -0
- package/src/radiance-hdr-loader.ts +36 -0
- package/src/texture-array-loader.ts +46 -0
- package/src/texture-cube-array-loader.ts +49 -0
- package/src/texture-cube-loader.ts +46 -0
- package/src/texture-loader.ts +49 -0
- package/src/workers/compressed-texture-worker.ts +0 -1
- package/dist/lib/utils/texture-formats.d.ts +0 -8
- package/dist/lib/utils/texture-formats.d.ts.map +0 -1
- package/dist/lib/utils/texture-formats.js +0 -50
- 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
|
+
}
|