@plasius/gpu-shared 0.1.16 → 0.1.17
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/CHANGELOG.md +6 -0
- package/README.md +3 -0
- package/dist/{chunk-6SOHFUOE.js → chunk-BXTHIQOO.js} +41 -3
- package/dist/chunk-BXTHIQOO.js.map +1 -0
- package/dist/chunk-UKCJ2AWJ.js +793 -0
- package/dist/chunk-UKCJ2AWJ.js.map +1 -0
- package/dist/{gltf-loader-B6VOWGBV.js → gltf-loader-FMRC3OEV.js} +2 -2
- package/dist/index.cjs +401 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -5
- package/dist/{product-studio-runtime-BYVBUWIN.js → product-studio-runtime-CTXA45RJ.js} +3 -3
- package/dist/{showcase-runtime-M6TEUYOG.js → showcase-runtime-OH3H6ZW2.js} +2 -2
- package/package.json +1 -1
- package/src/gltf-loader.js +447 -15
- package/src/index.d.ts +83 -0
- package/src/product-studio-runtime.js +53 -0
- package/dist/chunk-6SOHFUOE.js.map +0 -1
- package/dist/chunk-QVNRTWHB.js +0 -445
- package/dist/chunk-QVNRTWHB.js.map +0 -1
- /package/dist/{gltf-loader-B6VOWGBV.js.map → gltf-loader-FMRC3OEV.js.map} +0 -0
- /package/dist/{product-studio-runtime-BYVBUWIN.js.map → product-studio-runtime-CTXA45RJ.js.map} +0 -0
- /package/dist/{showcase-runtime-M6TEUYOG.js.map → showcase-runtime-OH3H6ZW2.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createProductStudioMeshes,
|
|
3
3
|
mountGpuProductStudio
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BXTHIQOO.js";
|
|
5
5
|
import {
|
|
6
6
|
GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE,
|
|
7
7
|
GPU_SHOWCASE_REALISTIC_MODELS_FEATURE,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-CH3ZS5TQ.js";
|
|
14
14
|
import {
|
|
15
15
|
resolveShowcaseAssetUrl
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-UKCJ2AWJ.js";
|
|
17
17
|
import "./chunk-2GM64LB6.js";
|
|
18
18
|
import "./chunk-DGUM43GV.js";
|
|
19
19
|
|
|
@@ -29,7 +29,7 @@ var showcaseFocusModes = Object.freeze([
|
|
|
29
29
|
]);
|
|
30
30
|
var showcaseDemoModes = Object.freeze(["harbor", "product-studio"]);
|
|
31
31
|
async function loadGltfModel(url) {
|
|
32
|
-
const module = await import("./gltf-loader-
|
|
32
|
+
const module = await import("./gltf-loader-FMRC3OEV.js");
|
|
33
33
|
return module.loadGltfModel(url);
|
|
34
34
|
}
|
|
35
35
|
function isProductStudioFeatureEnabled(featureFlags) {
|
|
@@ -58,7 +58,7 @@ async function mountGpuShowcase(options = {}) {
|
|
|
58
58
|
`${GPU_SHOWCASE_PRODUCT_STUDIO_FEATURE} must be enabled before Product Studio can mount.`
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
|
-
const productRuntimeLoader = typeof options.__productRuntimeLoader === "function" ? options.__productRuntimeLoader : () => import("./product-studio-runtime-
|
|
61
|
+
const productRuntimeLoader = typeof options.__productRuntimeLoader === "function" ? options.__productRuntimeLoader : () => import("./product-studio-runtime-CTXA45RJ.js");
|
|
62
62
|
const productModule = await productRuntimeLoader();
|
|
63
63
|
if (typeof productModule.mountGpuProductStudio !== "function") {
|
|
64
64
|
throw new Error("product runtime loader must provide mountGpuProductStudio.");
|
|
@@ -69,7 +69,7 @@ async function mountGpuShowcase(options = {}) {
|
|
|
69
69
|
delete productOptions.__featureFlags;
|
|
70
70
|
return productModule.mountGpuProductStudio(productOptions, options.__featureFlags);
|
|
71
71
|
}
|
|
72
|
-
const runtimeLoader = typeof options.__runtimeLoader === "function" ? options.__runtimeLoader : () => import("./showcase-runtime-
|
|
72
|
+
const runtimeLoader = typeof options.__runtimeLoader === "function" ? options.__runtimeLoader : () => import("./showcase-runtime-OH3H6ZW2.js");
|
|
73
73
|
const module = await runtimeLoader();
|
|
74
74
|
if (typeof module.mountGpuShowcase !== "function") {
|
|
75
75
|
throw new Error("showcase runtime loader must provide mountGpuShowcase.");
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createProductStudioMeshes,
|
|
3
3
|
mountGpuProductStudio
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-BXTHIQOO.js";
|
|
5
|
+
import "./chunk-UKCJ2AWJ.js";
|
|
6
6
|
import "./chunk-2GM64LB6.js";
|
|
7
7
|
import "./chunk-DGUM43GV.js";
|
|
8
8
|
export {
|
|
9
9
|
createProductStudioMeshes,
|
|
10
10
|
mountGpuProductStudio
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=product-studio-runtime-
|
|
12
|
+
//# sourceMappingURL=product-studio-runtime-CTXA45RJ.js.map
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
loadGltfModel,
|
|
8
8
|
resolveShowcaseAssetUrl
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UKCJ2AWJ.js";
|
|
10
10
|
import "./chunk-2GM64LB6.js";
|
|
11
11
|
import "./chunk-DGUM43GV.js";
|
|
12
12
|
|
|
@@ -3783,4 +3783,4 @@ export {
|
|
|
3783
3783
|
mountGpuShowcase,
|
|
3784
3784
|
showcaseFocusModes
|
|
3785
3785
|
};
|
|
3786
|
-
//# sourceMappingURL=showcase-runtime-
|
|
3786
|
+
//# sourceMappingURL=showcase-runtime-OH3H6ZW2.js.map
|
package/package.json
CHANGED
package/src/gltf-loader.js
CHANGED
|
@@ -55,6 +55,35 @@ function getTypeSize(type) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function getComponentByteSize(componentType) {
|
|
59
|
+
switch (componentType) {
|
|
60
|
+
case 5121:
|
|
61
|
+
return 1;
|
|
62
|
+
case 5123:
|
|
63
|
+
return 2;
|
|
64
|
+
case 5125:
|
|
65
|
+
case 5126:
|
|
66
|
+
return 4;
|
|
67
|
+
default:
|
|
68
|
+
throw new Error(`Unsupported glTF componentType: ${componentType}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readComponentValue(view, componentType, byteOffset) {
|
|
73
|
+
switch (componentType) {
|
|
74
|
+
case 5121:
|
|
75
|
+
return view.getUint8(byteOffset);
|
|
76
|
+
case 5123:
|
|
77
|
+
return view.getUint16(byteOffset, true);
|
|
78
|
+
case 5125:
|
|
79
|
+
return view.getUint32(byteOffset, true);
|
|
80
|
+
case 5126:
|
|
81
|
+
return view.getFloat32(byteOffset, true);
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`Unsupported glTF componentType: ${componentType}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
58
87
|
function readAccessor(document, accessorIndex, buffers) {
|
|
59
88
|
const accessor = document.accessors?.[accessorIndex];
|
|
60
89
|
if (!accessor) {
|
|
@@ -69,10 +98,30 @@ function readAccessor(document, accessorIndex, buffers) {
|
|
|
69
98
|
const buffer = buffers[bufferView.buffer];
|
|
70
99
|
const componentCount = getTypeSize(accessor.type);
|
|
71
100
|
const byteOffset = (bufferView.byteOffset ?? 0) + (accessor.byteOffset ?? 0);
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
101
|
+
const componentByteSize = getComponentByteSize(accessor.componentType);
|
|
102
|
+
const packedElementByteLength = componentCount * componentByteSize;
|
|
103
|
+
const byteStride = Math.max(bufferView.byteStride ?? packedElementByteLength, packedElementByteLength);
|
|
104
|
+
let values;
|
|
105
|
+
|
|
106
|
+
if (byteStride === packedElementByteLength) {
|
|
107
|
+
const valueCount = accessor.count * componentCount;
|
|
108
|
+
values = Array.from(
|
|
109
|
+
getComponentArray(accessor.componentType, buffer, byteOffset, valueCount)
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
const view = new DataView(buffer, byteOffset);
|
|
113
|
+
values = new Array(accessor.count * componentCount);
|
|
114
|
+
for (let index = 0; index < accessor.count; index += 1) {
|
|
115
|
+
const elementOffset = index * byteStride;
|
|
116
|
+
for (let componentIndex = 0; componentIndex < componentCount; componentIndex += 1) {
|
|
117
|
+
values[index * componentCount + componentIndex] = readComponentValue(
|
|
118
|
+
view,
|
|
119
|
+
accessor.componentType,
|
|
120
|
+
elementOffset + componentIndex * componentByteSize
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
76
125
|
|
|
77
126
|
if (accessor.normalized) {
|
|
78
127
|
const scale = getNormalizationScale(accessor.componentType);
|
|
@@ -82,11 +131,258 @@ function readAccessor(document, accessorIndex, buffers) {
|
|
|
82
131
|
return values;
|
|
83
132
|
}
|
|
84
133
|
|
|
85
|
-
function
|
|
134
|
+
async function decodeImagePixels(blob, urlLabel = "glTF texture") {
|
|
135
|
+
if (typeof createImageBitmap === "function") {
|
|
136
|
+
const bitmap = await createImageBitmap(blob);
|
|
137
|
+
try {
|
|
138
|
+
const canvas =
|
|
139
|
+
typeof OffscreenCanvas === "function"
|
|
140
|
+
? new OffscreenCanvas(bitmap.width, bitmap.height)
|
|
141
|
+
: typeof document !== "undefined"
|
|
142
|
+
? Object.assign(document.createElement("canvas"), {
|
|
143
|
+
width: bitmap.width,
|
|
144
|
+
height: bitmap.height,
|
|
145
|
+
})
|
|
146
|
+
: null;
|
|
147
|
+
const context = canvas?.getContext?.("2d", { willReadFrequently: true });
|
|
148
|
+
if (!context) {
|
|
149
|
+
throw new Error("Unable to create 2D context for glTF texture decode.");
|
|
150
|
+
}
|
|
151
|
+
context.drawImage(bitmap, 0, 0);
|
|
152
|
+
const imageData = context.getImageData(0, 0, bitmap.width, bitmap.height);
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
width: bitmap.width,
|
|
155
|
+
height: bitmap.height,
|
|
156
|
+
data: imageData.data,
|
|
157
|
+
});
|
|
158
|
+
} finally {
|
|
159
|
+
bitmap.close?.();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (typeof document === "undefined") {
|
|
164
|
+
throw new Error(`Unable to decode ${urlLabel}: browser image decode APIs are unavailable.`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
168
|
+
try {
|
|
169
|
+
const image = await new Promise((resolve, reject) => {
|
|
170
|
+
const element = new Image();
|
|
171
|
+
element.onload = () => resolve(element);
|
|
172
|
+
element.onerror = () => reject(new Error(`Failed to decode ${urlLabel}.`));
|
|
173
|
+
element.src = objectUrl;
|
|
174
|
+
});
|
|
175
|
+
const canvas = Object.assign(document.createElement("canvas"), {
|
|
176
|
+
width: image.naturalWidth || image.width,
|
|
177
|
+
height: image.naturalHeight || image.height,
|
|
178
|
+
});
|
|
179
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
180
|
+
if (!context) {
|
|
181
|
+
throw new Error("Unable to create 2D context for glTF texture decode.");
|
|
182
|
+
}
|
|
183
|
+
context.drawImage(image, 0, 0);
|
|
184
|
+
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
185
|
+
return Object.freeze({
|
|
186
|
+
width: canvas.width,
|
|
187
|
+
height: canvas.height,
|
|
188
|
+
data: imageData.data,
|
|
189
|
+
});
|
|
190
|
+
} finally {
|
|
191
|
+
URL.revokeObjectURL(objectUrl);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function sliceBufferView(document, bufferViewIndex, buffers) {
|
|
196
|
+
const bufferView = document.bufferViews?.[bufferViewIndex];
|
|
197
|
+
if (!bufferView) {
|
|
198
|
+
throw new Error(`glTF bufferView ${bufferViewIndex} is missing.`);
|
|
199
|
+
}
|
|
200
|
+
const buffer = buffers[bufferView.buffer];
|
|
201
|
+
if (!buffer) {
|
|
202
|
+
throw new Error(`glTF buffer ${bufferView.buffer} is missing.`);
|
|
203
|
+
}
|
|
204
|
+
const start = bufferView.byteOffset ?? 0;
|
|
205
|
+
const end = start + (bufferView.byteLength ?? 0);
|
|
206
|
+
return buffer.slice(start, end);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function loadImageResource(document, image, index, buffers, baseUrl) {
|
|
210
|
+
if (typeof image?.uri === "string") {
|
|
211
|
+
const response = await fetch(new URL(image.uri, baseUrl));
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
throw new Error(`Failed to load glTF texture: ${response.status} ${response.statusText}`);
|
|
214
|
+
}
|
|
215
|
+
return decodeImagePixels(
|
|
216
|
+
await response.blob(),
|
|
217
|
+
`glTF texture ${index}${image.uri ? ` (${image.uri})` : ""}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof image?.bufferView === "number") {
|
|
222
|
+
const bytes = sliceBufferView(document, image.bufferView, buffers);
|
|
223
|
+
return decodeImagePixels(
|
|
224
|
+
new Blob([bytes], { type: image.mimeType ?? "application/octet-stream" }),
|
|
225
|
+
`glTF texture ${index}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function normalizeTextureTransformPair(value, fallback) {
|
|
233
|
+
if (!Array.isArray(value) || value.length < 2) {
|
|
234
|
+
return fallback;
|
|
235
|
+
}
|
|
236
|
+
return [
|
|
237
|
+
Number.isFinite(value[0]) ? Number(value[0]) : fallback[0],
|
|
238
|
+
Number.isFinite(value[1]) ? Number(value[1]) : fallback[1],
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readTextureTransform(textureRef) {
|
|
243
|
+
const transformExtension = textureRef?.extensions?.KHR_texture_transform ?? null;
|
|
244
|
+
return {
|
|
245
|
+
texCoord:
|
|
246
|
+
typeof transformExtension?.texCoord === "number"
|
|
247
|
+
? transformExtension.texCoord
|
|
248
|
+
: textureRef?.texCoord ?? 0,
|
|
249
|
+
offset: normalizeTextureTransformPair(transformExtension?.offset, [0, 0]),
|
|
250
|
+
scale: normalizeTextureTransformPair(transformExtension?.scale, [1, 1]),
|
|
251
|
+
rotation: Number.isFinite(transformExtension?.rotation) ? Number(transformExtension.rotation) : 0,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function wrapTextureCoordinate(value) {
|
|
256
|
+
return ((value % 1) + 1) % 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function transformTextureCoordinate(uv, transform) {
|
|
260
|
+
const scaledU = uv[0] * transform.scale[0];
|
|
261
|
+
const scaledV = uv[1] * transform.scale[1];
|
|
262
|
+
const cosine = Math.cos(transform.rotation);
|
|
263
|
+
const sine = Math.sin(transform.rotation);
|
|
264
|
+
return [
|
|
265
|
+
scaledU * cosine - scaledV * sine + transform.offset[0],
|
|
266
|
+
scaledU * sine + scaledV * cosine + transform.offset[1],
|
|
267
|
+
];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function readTexturePixel(data, width, height, x, y) {
|
|
271
|
+
const clampedX = Math.min(width - 1, Math.max(0, x));
|
|
272
|
+
const clampedY = Math.min(height - 1, Math.max(0, y));
|
|
273
|
+
const offset = (clampedY * width + clampedX) * 4;
|
|
274
|
+
return [
|
|
275
|
+
data[offset] ?? 0,
|
|
276
|
+
data[offset + 1] ?? 0,
|
|
277
|
+
data[offset + 2] ?? 0,
|
|
278
|
+
data[offset + 3] ?? 255,
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function mixChannel(a, b, weight) {
|
|
283
|
+
return a + (b - a) * weight;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function sampleTexturePixel(data, width, height, uv) {
|
|
287
|
+
const u = wrapTextureCoordinate(uv[0]);
|
|
288
|
+
const v = wrapTextureCoordinate(uv[1]);
|
|
289
|
+
const sourceX = u * Math.max(width - 1, 0);
|
|
290
|
+
const sourceY = (1 - v) * Math.max(height - 1, 0);
|
|
291
|
+
const x0 = Math.floor(sourceX);
|
|
292
|
+
const y0 = Math.floor(sourceY);
|
|
293
|
+
const x1 = Math.min(width - 1, x0 + 1);
|
|
294
|
+
const y1 = Math.min(height - 1, y0 + 1);
|
|
295
|
+
const tx = sourceX - x0;
|
|
296
|
+
const ty = sourceY - y0;
|
|
297
|
+
const topLeft = readTexturePixel(data, width, height, x0, y0);
|
|
298
|
+
const topRight = readTexturePixel(data, width, height, x1, y0);
|
|
299
|
+
const bottomLeft = readTexturePixel(data, width, height, x0, y1);
|
|
300
|
+
const bottomRight = readTexturePixel(data, width, height, x1, y1);
|
|
301
|
+
return [0, 1, 2, 3].map((channelIndex) => {
|
|
302
|
+
const top = mixChannel(topLeft[channelIndex], topRight[channelIndex], tx);
|
|
303
|
+
const bottom = mixChannel(bottomLeft[channelIndex], bottomRight[channelIndex], tx);
|
|
304
|
+
return mixChannel(top, bottom, ty);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function applyTextureTransformToPixels(pixels, transform) {
|
|
309
|
+
const isIdentityTransform =
|
|
310
|
+
transform.offset[0] === 0 &&
|
|
311
|
+
transform.offset[1] === 0 &&
|
|
312
|
+
transform.scale[0] === 1 &&
|
|
313
|
+
transform.scale[1] === 1 &&
|
|
314
|
+
transform.rotation === 0;
|
|
315
|
+
if (isIdentityTransform) {
|
|
316
|
+
return pixels;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const transformedData = new Uint8ClampedArray(pixels.data.length);
|
|
320
|
+
for (let y = 0; y < pixels.height; y += 1) {
|
|
321
|
+
const outputV = pixels.height > 1 ? 1 - y / (pixels.height - 1) : 0;
|
|
322
|
+
for (let x = 0; x < pixels.width; x += 1) {
|
|
323
|
+
const outputU = pixels.width > 1 ? x / (pixels.width - 1) : 0;
|
|
324
|
+
const sourcePixel = sampleTexturePixel(
|
|
325
|
+
pixels.data,
|
|
326
|
+
pixels.width,
|
|
327
|
+
pixels.height,
|
|
328
|
+
transformTextureCoordinate([outputU, outputV], transform)
|
|
329
|
+
);
|
|
330
|
+
const offset = (y * pixels.width + x) * 4;
|
|
331
|
+
transformedData[offset] = sourcePixel[0];
|
|
332
|
+
transformedData[offset + 1] = sourcePixel[1];
|
|
333
|
+
transformedData[offset + 2] = sourcePixel[2];
|
|
334
|
+
transformedData[offset + 3] = sourcePixel[3];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return Object.freeze({
|
|
339
|
+
width: pixels.width,
|
|
340
|
+
height: pixels.height,
|
|
341
|
+
data: transformedData,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function getMaterialTexture(document, textureRef, imageResources) {
|
|
346
|
+
if (!textureRef || typeof textureRef.index !== "number") {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
const texture = document.textures?.[textureRef.index] ?? null;
|
|
350
|
+
const sourceIndex = texture?.source;
|
|
351
|
+
if (typeof sourceIndex !== "number") {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const pixels = imageResources.get(sourceIndex) ?? null;
|
|
355
|
+
if (!pixels) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const transform = readTextureTransform(textureRef);
|
|
360
|
+
const transformedPixels = applyTextureTransformToPixels(pixels, transform);
|
|
361
|
+
return Object.freeze({
|
|
362
|
+
texCoord: transform.texCoord,
|
|
363
|
+
scale: textureRef.scale,
|
|
364
|
+
strength: textureRef.strength,
|
|
365
|
+
width: transformedPixels.width,
|
|
366
|
+
height: transformedPixels.height,
|
|
367
|
+
data: transformedPixels.data,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function getMaterialInfo(document, primitive, imageResources) {
|
|
86
372
|
const material = document.materials?.[primitive.material] ?? null;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const emissive = material?.emissiveFactor
|
|
373
|
+
const pbr = material?.pbrMetallicRoughness ?? null;
|
|
374
|
+
const factor = pbr?.baseColorFactor ?? [0.56, 0.33, 0.22, 1];
|
|
375
|
+
const emissive = Array.isArray(material?.emissiveFactor) ? material.emissiveFactor : [0, 0, 0];
|
|
376
|
+
const extensions = material?.extensions ?? {};
|
|
377
|
+
const specular = extensions.KHR_materials_specular ?? null;
|
|
378
|
+
const transmission = extensions.KHR_materials_transmission ?? null;
|
|
379
|
+
const ior = extensions.KHR_materials_ior ?? null;
|
|
380
|
+
const clearcoat = extensions.KHR_materials_clearcoat ?? null;
|
|
381
|
+
const sheen = extensions.KHR_materials_sheen ?? null;
|
|
382
|
+
const volume = extensions.KHR_materials_volume ?? null;
|
|
383
|
+
const iridescence = extensions.KHR_materials_iridescence ?? null;
|
|
384
|
+
const anisotropy = extensions.KHR_materials_anisotropy ?? null;
|
|
385
|
+
const dispersion = extensions.KHR_materials_dispersion ?? null;
|
|
90
386
|
|
|
91
387
|
return Object.freeze({
|
|
92
388
|
name: material?.name ?? "default-material",
|
|
@@ -97,18 +393,139 @@ function getMaterialInfo(document, primitive) {
|
|
|
97
393
|
a: factor[3] ?? 1,
|
|
98
394
|
}),
|
|
99
395
|
roughness:
|
|
100
|
-
typeof
|
|
101
|
-
?
|
|
396
|
+
typeof pbr?.roughnessFactor === "number"
|
|
397
|
+
? pbr.roughnessFactor
|
|
102
398
|
: 0.92,
|
|
103
399
|
metallic:
|
|
104
|
-
typeof
|
|
105
|
-
?
|
|
400
|
+
typeof pbr?.metallicFactor === "number"
|
|
401
|
+
? pbr.metallicFactor
|
|
106
402
|
: 0.08,
|
|
403
|
+
opacity: factor[3] ?? 1,
|
|
107
404
|
emissive: Object.freeze({
|
|
108
405
|
r: emissive[0] ?? 0,
|
|
109
406
|
g: emissive[1] ?? 0,
|
|
110
407
|
b: emissive[2] ?? 0,
|
|
408
|
+
a: 1,
|
|
111
409
|
}),
|
|
410
|
+
baseColorTexture: getMaterialTexture(document, pbr?.baseColorTexture, imageResources),
|
|
411
|
+
metallicRoughnessTexture: getMaterialTexture(
|
|
412
|
+
document,
|
|
413
|
+
pbr?.metallicRoughnessTexture,
|
|
414
|
+
imageResources
|
|
415
|
+
),
|
|
416
|
+
normalTexture: getMaterialTexture(document, material?.normalTexture, imageResources),
|
|
417
|
+
occlusionTexture: getMaterialTexture(document, material?.occlusionTexture, imageResources),
|
|
418
|
+
emissiveTexture: getMaterialTexture(document, material?.emissiveTexture, imageResources),
|
|
419
|
+
specular:
|
|
420
|
+
typeof specular?.specularFactor === "number"
|
|
421
|
+
? specular.specularFactor
|
|
422
|
+
: 1,
|
|
423
|
+
specularColor: Object.freeze(
|
|
424
|
+
Array.isArray(specular?.specularColorFactor)
|
|
425
|
+
? [...specular.specularColorFactor]
|
|
426
|
+
: [1, 1, 1]
|
|
427
|
+
),
|
|
428
|
+
specularTexture: getMaterialTexture(document, specular?.specularTexture, imageResources),
|
|
429
|
+
specularColorTexture: getMaterialTexture(
|
|
430
|
+
document,
|
|
431
|
+
specular?.specularColorTexture,
|
|
432
|
+
imageResources
|
|
433
|
+
),
|
|
434
|
+
transmission:
|
|
435
|
+
typeof transmission?.transmissionFactor === "number"
|
|
436
|
+
? transmission.transmissionFactor
|
|
437
|
+
: 0,
|
|
438
|
+
transmissionTexture: getMaterialTexture(
|
|
439
|
+
document,
|
|
440
|
+
transmission?.transmissionTexture,
|
|
441
|
+
imageResources
|
|
442
|
+
),
|
|
443
|
+
ior: typeof ior?.ior === "number" ? ior.ior : 1.45,
|
|
444
|
+
attenuationDistance:
|
|
445
|
+
typeof volume?.attenuationDistance === "number"
|
|
446
|
+
? volume.attenuationDistance
|
|
447
|
+
: null,
|
|
448
|
+
attenuationColor: Object.freeze(
|
|
449
|
+
Array.isArray(volume?.attenuationColor)
|
|
450
|
+
? [...volume.attenuationColor]
|
|
451
|
+
: [1, 1, 1]
|
|
452
|
+
),
|
|
453
|
+
thickness:
|
|
454
|
+
typeof volume?.thicknessFactor === "number"
|
|
455
|
+
? volume.thicknessFactor
|
|
456
|
+
: 0,
|
|
457
|
+
thicknessTexture: getMaterialTexture(document, volume?.thicknessTexture, imageResources),
|
|
458
|
+
clearcoat:
|
|
459
|
+
typeof clearcoat?.clearcoatFactor === "number"
|
|
460
|
+
? clearcoat.clearcoatFactor
|
|
461
|
+
: 0,
|
|
462
|
+
clearcoatTexture: getMaterialTexture(document, clearcoat?.clearcoatTexture, imageResources),
|
|
463
|
+
clearcoatRoughness:
|
|
464
|
+
typeof clearcoat?.clearcoatRoughnessFactor === "number"
|
|
465
|
+
? clearcoat.clearcoatRoughnessFactor
|
|
466
|
+
: 0.08,
|
|
467
|
+
clearcoatRoughnessTexture: getMaterialTexture(
|
|
468
|
+
document,
|
|
469
|
+
clearcoat?.clearcoatRoughnessTexture,
|
|
470
|
+
imageResources
|
|
471
|
+
),
|
|
472
|
+
clearcoatNormalTexture: getMaterialTexture(
|
|
473
|
+
document,
|
|
474
|
+
clearcoat?.clearcoatNormalTexture,
|
|
475
|
+
imageResources
|
|
476
|
+
),
|
|
477
|
+
sheenColor: Object.freeze(
|
|
478
|
+
Array.isArray(sheen?.sheenColorFactor) ? [...sheen.sheenColorFactor] : [0, 0, 0]
|
|
479
|
+
),
|
|
480
|
+
sheenColorTexture: getMaterialTexture(document, sheen?.sheenColorTexture, imageResources),
|
|
481
|
+
sheenRoughness:
|
|
482
|
+
typeof sheen?.sheenRoughnessFactor === "number"
|
|
483
|
+
? sheen.sheenRoughnessFactor
|
|
484
|
+
: 0,
|
|
485
|
+
sheenRoughnessTexture: getMaterialTexture(
|
|
486
|
+
document,
|
|
487
|
+
sheen?.sheenRoughnessTexture,
|
|
488
|
+
imageResources
|
|
489
|
+
),
|
|
490
|
+
iridescence:
|
|
491
|
+
typeof iridescence?.iridescenceFactor === "number"
|
|
492
|
+
? iridescence.iridescenceFactor
|
|
493
|
+
: 0,
|
|
494
|
+
iridescenceTexture: getMaterialTexture(
|
|
495
|
+
document,
|
|
496
|
+
iridescence?.iridescenceTexture,
|
|
497
|
+
imageResources
|
|
498
|
+
),
|
|
499
|
+
iridescenceIor:
|
|
500
|
+
typeof iridescence?.iridescenceIor === "number"
|
|
501
|
+
? iridescence.iridescenceIor
|
|
502
|
+
: 1.3,
|
|
503
|
+
iridescenceThicknessMinimum:
|
|
504
|
+
typeof iridescence?.iridescenceThicknessMinimum === "number"
|
|
505
|
+
? iridescence.iridescenceThicknessMinimum
|
|
506
|
+
: 100,
|
|
507
|
+
iridescenceThicknessMaximum:
|
|
508
|
+
typeof iridescence?.iridescenceThicknessMaximum === "number"
|
|
509
|
+
? iridescence.iridescenceThicknessMaximum
|
|
510
|
+
: 400,
|
|
511
|
+
iridescenceThicknessTexture: getMaterialTexture(
|
|
512
|
+
document,
|
|
513
|
+
iridescence?.iridescenceThicknessTexture,
|
|
514
|
+
imageResources
|
|
515
|
+
),
|
|
516
|
+
anisotropy:
|
|
517
|
+
typeof anisotropy?.anisotropyStrength === "number"
|
|
518
|
+
? anisotropy.anisotropyStrength
|
|
519
|
+
: 0,
|
|
520
|
+
anisotropyRotation:
|
|
521
|
+
typeof anisotropy?.anisotropyRotation === "number"
|
|
522
|
+
? anisotropy.anisotropyRotation
|
|
523
|
+
: 0,
|
|
524
|
+
anisotropyTexture: getMaterialTexture(document, anisotropy?.anisotropyTexture, imageResources),
|
|
525
|
+
dispersion:
|
|
526
|
+
typeof dispersion?.dispersion === "number"
|
|
527
|
+
? dispersion.dispersion
|
|
528
|
+
: 0,
|
|
112
529
|
});
|
|
113
530
|
}
|
|
114
531
|
|
|
@@ -269,7 +686,7 @@ function transformNormal(normal, matrix) {
|
|
|
269
686
|
return [transformed[0] / length, transformed[1] / length, transformed[2] / length];
|
|
270
687
|
}
|
|
271
688
|
|
|
272
|
-
function collectScenePrimitives(document, buffers) {
|
|
689
|
+
function collectScenePrimitives(document, buffers, imageResources) {
|
|
273
690
|
const scene = document.scenes?.[document.scene ?? 0];
|
|
274
691
|
if (!scene || !Array.isArray(scene.nodes) || scene.nodes.length === 0) {
|
|
275
692
|
throw new Error("glTF demo asset must expose a default scene with at least one node.");
|
|
@@ -312,6 +729,10 @@ function collectScenePrimitives(document, buffers) {
|
|
|
312
729
|
typeof primitive.attributes.COLOR_0 === "number"
|
|
313
730
|
? readAccessor(document, primitive.attributes.COLOR_0, buffers)
|
|
314
731
|
: null;
|
|
732
|
+
const uvs =
|
|
733
|
+
typeof primitive.attributes.TEXCOORD_0 === "number"
|
|
734
|
+
? readAccessor(document, primitive.attributes.TEXCOORD_0, buffers)
|
|
735
|
+
: null;
|
|
315
736
|
const transformedPositions = [];
|
|
316
737
|
const transformedNormals = [];
|
|
317
738
|
|
|
@@ -335,7 +756,7 @@ function collectScenePrimitives(document, buffers) {
|
|
|
335
756
|
typeof primitive.indices === "number"
|
|
336
757
|
? readAccessor(document, primitive.indices, buffers).map((value) => Number(value))
|
|
337
758
|
: Array.from({ length: transformedPositions.length / 3 }, (_, index) => index);
|
|
338
|
-
const material = getMaterialInfo(document, primitive);
|
|
759
|
+
const material = getMaterialInfo(document, primitive, imageResources);
|
|
339
760
|
const primitiveName =
|
|
340
761
|
`${node.name ?? mesh.name ?? "mesh"}-${primitiveIndex}`;
|
|
341
762
|
|
|
@@ -348,6 +769,7 @@ function collectScenePrimitives(document, buffers) {
|
|
|
348
769
|
transformedNormals.length > 0
|
|
349
770
|
? Object.freeze(transformedNormals)
|
|
350
771
|
: null,
|
|
772
|
+
uvs: uvs ? Object.freeze(uvs) : null,
|
|
351
773
|
colors: colors ? Object.freeze(colors) : null,
|
|
352
774
|
material,
|
|
353
775
|
bounds: computeBounds(transformedPositions),
|
|
@@ -412,7 +834,17 @@ async function buildGltfModel(document, baseUrl) {
|
|
|
412
834
|
})
|
|
413
835
|
);
|
|
414
836
|
|
|
415
|
-
const
|
|
837
|
+
const imageResources = new Map();
|
|
838
|
+
await Promise.all(
|
|
839
|
+
(document.images ?? []).map(async (image, index) => {
|
|
840
|
+
const pixels = await loadImageResource(document, image, index, buffers, baseUrl);
|
|
841
|
+
if (pixels) {
|
|
842
|
+
imageResources.set(index, pixels);
|
|
843
|
+
}
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
const scene = collectScenePrimitives(document, buffers, imageResources);
|
|
416
848
|
const aggregatePositions = [];
|
|
417
849
|
const aggregateIndices = [];
|
|
418
850
|
|