@plasius/gpu-shared 0.1.11 → 0.1.13
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 +36 -3
- package/README.md +55 -1
- package/assets/brigantine.gltf +549 -24
- package/assets/cutter.gltf +538 -0
- package/assets/harbor-dock.gltf +680 -0
- package/assets/lighthouse.gltf +604 -0
- package/dist/chunk-2FIFSBB4.js +74 -0
- package/dist/chunk-2FIFSBB4.js.map +1 -0
- package/dist/chunk-DABW627O.js +113 -0
- package/dist/chunk-DABW627O.js.map +1 -0
- package/dist/chunk-DQX4DXBR.js +369 -0
- package/dist/chunk-DQX4DXBR.js.map +1 -0
- package/dist/chunk-NCPJWLX3.js +17 -0
- package/dist/chunk-NCPJWLX3.js.map +1 -0
- package/dist/gltf-loader-WAM23F37.js +9 -0
- package/dist/index.cjs +1255 -279
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +19 -6
- package/dist/index.js.map +1 -1
- package/dist/showcase-inline-assets-B7U7VX5H.js +7 -0
- package/dist/{showcase-runtime-2ZNPKD7D.js → showcase-runtime-PN7N3FZY.js} +808 -237
- package/dist/showcase-runtime-PN7N3FZY.js.map +1 -0
- package/package.json +15 -1
- package/src/asset-url.js +62 -11
- package/src/feature-flags.js +1 -0
- package/src/gltf-loader.js +322 -32
- package/src/i18n.js +71 -0
- package/src/index.d.ts +115 -1
- package/src/index.js +9 -1
- package/src/showcase-inline-assets.js +3 -0
- package/src/showcase-runtime.js +912 -188
- package/src/translations/en-GB.js +55 -0
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-OTCJ3VOK.js +0 -35
- package/dist/chunk-OTCJ3VOK.js.map +0 -1
- package/dist/chunk-QBMXJ3V2.js +0 -142
- package/dist/chunk-QBMXJ3V2.js.map +0 -1
- package/dist/gltf-loader-LKALCZAV.js +0 -8
- package/dist/showcase-runtime-2ZNPKD7D.js.map +0 -1
- /package/dist/{chunk-DGUM43GV.js.map → gltf-loader-WAM23F37.js.map} +0 -0
- /package/dist/{gltf-loader-LKALCZAV.js.map → showcase-inline-assets-B7U7VX5H.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/gpu-shared",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Shared browser-safe demo runtime and asset helpers for the Plasius gpu-* package family.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -23,12 +23,16 @@
|
|
|
23
23
|
"require": "./dist/index.cjs"
|
|
24
24
|
},
|
|
25
25
|
"./assets/brigantine.gltf": "./assets/brigantine.gltf",
|
|
26
|
+
"./assets/cutter.gltf": "./assets/cutter.gltf",
|
|
27
|
+
"./assets/lighthouse.gltf": "./assets/lighthouse.gltf",
|
|
28
|
+
"./assets/harbor-dock.gltf": "./assets/harbor-dock.gltf",
|
|
26
29
|
"./package.json": "./package.json"
|
|
27
30
|
},
|
|
28
31
|
"scripts": {
|
|
29
32
|
"build": "tsup",
|
|
30
33
|
"demo": "python3 -m http.server --directory ..",
|
|
31
34
|
"demo:example": "node demo/example.js",
|
|
35
|
+
"generate:assets": "node scripts/generate-showcase-assets.mjs",
|
|
32
36
|
"typecheck": "node --check src/index.js && node --check src/showcase-runtime.js && node --check src/gltf-loader.js && node --check demo/main.js",
|
|
33
37
|
"audit:eslint": "eslint . --max-warnings=0",
|
|
34
38
|
"audit:deps": "npm ls --all --omit=optional --omit=peer > /dev/null 2>&1 || true",
|
|
@@ -60,11 +64,21 @@
|
|
|
60
64
|
"@plasius/gpu-performance": "^0.1.6",
|
|
61
65
|
"@plasius/gpu-physics": "^0.1.13"
|
|
62
66
|
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@plasius/translations": "^1.0.17"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"@plasius/translations": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
63
75
|
"devDependencies": {
|
|
64
76
|
"@eslint/js": "^10.0.1",
|
|
77
|
+
"@plasius/translations": "^1.0.17",
|
|
65
78
|
"c8": "^11.0.0",
|
|
66
79
|
"eslint": "^10.3.0",
|
|
67
80
|
"globals": "^17.6.0",
|
|
81
|
+
"react": "^19.2.6",
|
|
68
82
|
"tsup": "^8.5.1",
|
|
69
83
|
"typescript": "^6.0.3"
|
|
70
84
|
},
|
package/src/asset-url.js
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
"data:application/json;base64,ewogICJhc3NldCI6IHsKICAgICJ2ZXJzaW9uIjogIjIuMCIsCiAgICAiZ2VuZXJhdG9yIjogIlBsYXNpdXMgZGVtbyBhc3NldCBnZW5lcmF0b3IiCiAgfSwKICAic2NlbmUiOiAwLAogICJzY2VuZXMiOiBbCiAgICB7CiAgICAgICJub2RlcyI6IFswXQogICAgfQogIF0sCiAgIm5vZGVzIjogWwogICAgewogICAgICAibWVzaCI6IDAsCiAgICAgICJuYW1lIjogImJyaWdhbnRpbmUiLAogICAgICAiZXh0cmFzIjogewogICAgICAgICJwaHlzaWNzIjogewogICAgICAgICAgInNoYXBlIjogImJveCIsCiAgICAgICAgICAiaGFsZkV4dGVudHMiOiBbMS4zNSwgMC45NSwgMy45XSwKICAgICAgICAgICJtYXNzIjogMzIwMCwKICAgICAgICAgICJyZXN0aXR1dGlvbiI6IDAuMjIsCiAgICAgICAgICAibGluZWFyRGFtcGluZyI6IDAuMDQsCiAgICAgICAgICAiYW5ndWxhckRhbXBpbmciOiAwLjA4LAogICAgICAgICAgIndhdGVybGluZSI6IDAuNDIKICAgICAgICB9CiAgICAgIH0KICAgIH0KICBdLAogICJtZXNoZXMiOiBbCiAgICB7CiAgICAgICJuYW1lIjogImJyaWdhbnRpbmUtaHVsbCIsCiAgICAgICJwcmltaXRpdmVzIjogWwogICAgICAgIHsKICAgICAgICAgICJhdHRyaWJ1dGVzIjogewogICAgICAgICAgICAiUE9TSVRJT04iOiAwCiAgICAgICAgICB9LAogICAgICAgICAgImluZGljZXMiOiAxLAogICAgICAgICAgIm1hdGVyaWFsIjogMAogICAgICAgIH0KICAgICAgXQogICAgfQogIF0sCiAgIm1hdGVyaWFscyI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAicGFpbnRlZC1odWxsIiwKICAgICAgInBick1ldGFsbGljUm91Z2huZXNzIjogewogICAgICAgICJiYXNlQ29sb3JGYWN0b3IiOiBbMC41NiwgMC4zMywgMC4yMiwgMV0sCiAgICAgICAgIm1ldGFsbGljRmFjdG9yIjogMC4wOCwKICAgICAgICAicm91Z2huZXNzRmFjdG9yIjogMC45MgogICAgICB9CiAgICB9CiAgXSwKICAiYnVmZmVycyI6IFsKICAgIHsKICAgICAgInVyaSI6ICJkYXRhOmFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbTtiYXNlNjQsbXBtWnZ3QUFBTC9OekV6QW1wbVpQd0FBQUwvTnpFekF6Y3lzdnpNenM3NHpNN08vemN5c1B6TXpzNzR6TTdPL0FBQ2d2ODNNVEwzTnpNdy9BQUNnUDgzTVRMM056TXcvQUFBQUFPeFJPTDR6TTROQUFBQUFBR1ptNWo0QUFIQkFNek56dnpNenN6NmFtUm5BTXpOelB6TXpzejZhbVJuQXpjeE12ejBLMXo3TnpFdy96Y3hNUHowSzF6N056RXcvQUFBQUFETXpjejltWm1hL0FBQUNBQU1BQUFBREFBRUFBZ0FFQUFVQUFnQUZBQU1BQkFBSEFBVUFCQUFHQUFjQUJRQUhBQVlBQUFBQkFBa0FBQUFKQUFnQUNBQUpBQXdBQWdBSUFBd0FBd0FNQUFrQUFnQU1BQW9BQXdBTEFBd0FBZ0FLQUFRQUF3QUZBQXNBQ2dBTUFBc0FBQUFJQUFJQUFRQURBQWtBQkFBS0FBWUFCUUFHQUFzQUFnQUtBQXNBQWdBTEFBTUEiLAogICAgICAiYnl0ZUxlbmd0aCI6IDI5NAogICAgfQogIF0sCiAgImJ1ZmZlclZpZXdzIjogWwogICAgewogICAgICAiYnVmZmVyIjogMCwKICAgICAgImJ5dGVPZmZzZXQiOiAwLAogICAgICAiYnl0ZUxlbmd0aCI6IDE1NiwKICAgICAgInRhcmdldCI6IDM0OTYyCiAgICB9LAogICAgewogICAgICAiYnVmZmVyIjogMCwKICAgICAgImJ5dGVPZmZzZXQiOiAxNTYsCiAgICAgICJieXRlTGVuZ3RoIjogMTM4LAogICAgICAidGFyZ2V0IjogMzQ5NjMKICAgIH0KICBdLAogICJhY2Nlc3NvcnMiOiBbCiAgICB7CiAgICAgICJidWZmZXJWaWV3IjogMCwKICAgICAgImJ5dGVPZmZzZXQiOiAwLAogICAgICAiY29tcG9uZW50VHlwZSI6IDUxMjYsCiAgICAgICJjb3VudCI6IDEzLAogICAgICAidHlwZSI6ICJWRUMzIiwKICAgICAgIm1pbiI6IFstMS4zNSwgLTAuNSwgLTMuMl0sCiAgICAgICJtYXgiOiBbMS4zNSwgMC45NSwgNC4xXQogICAgfSwKICAgIHsKICAgICAgImJ1ZmZlclZpZXciOiAxLAogICAgICAiYnl0ZU9mZnNldCI6IDAsCiAgICAgICJjb21wb25lbnRUeXBlIjogNTEyMywKICAgICAgImNvdW50IjogNjksCiAgICAgICJ0eXBlIjogIlNDQUxBUiIsCiAgICAgICJtYXgiOiBbMTJdLAogICAgICAibWluIjogWzBdCiAgICB9CiAgXQp9Cg==";
|
|
1
|
+
import { INLINE_SHOWCASE_ASSET_URLS } from "./showcase-inline-assets.js";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const SHOWCASE_ASSET_FILES = Object.freeze({
|
|
4
|
+
brigantine: "brigantine.gltf",
|
|
5
|
+
cutter: "cutter.gltf",
|
|
6
|
+
lighthouse: "lighthouse.gltf",
|
|
7
|
+
"harbor-dock": "harbor-dock.gltf",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
function createInlineShowcaseAssetUrl(assetName) {
|
|
11
|
+
const inlineUrl = INLINE_SHOWCASE_ASSET_URLS[assetName];
|
|
12
|
+
return inlineUrl ? new URL(inlineUrl) : null;
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
function getBrowserBaseUrl() {
|
|
9
|
-
if (
|
|
16
|
+
if (
|
|
17
|
+
typeof document !== "undefined" &&
|
|
18
|
+
typeof document.baseURI === "string" &&
|
|
19
|
+
document.baseURI.length > 0
|
|
20
|
+
) {
|
|
10
21
|
return document.baseURI;
|
|
11
22
|
}
|
|
12
23
|
if (
|
|
@@ -19,19 +30,59 @@ function getBrowserBaseUrl() {
|
|
|
19
30
|
return null;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
function normalizeAssetName(assetName) {
|
|
34
|
+
return typeof assetName === "string" && assetName in SHOWCASE_ASSET_FILES
|
|
35
|
+
? assetName
|
|
36
|
+
: "brigantine";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseResolveArgs(baseUrlOrAssetName, assetName) {
|
|
40
|
+
if (
|
|
41
|
+
typeof baseUrlOrAssetName === "string" &&
|
|
42
|
+
baseUrlOrAssetName in SHOWCASE_ASSET_FILES &&
|
|
43
|
+
typeof assetName === "undefined"
|
|
44
|
+
) {
|
|
45
|
+
return {
|
|
46
|
+
baseUrl: import.meta.url,
|
|
47
|
+
assetName: baseUrlOrAssetName,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
baseUrl: baseUrlOrAssetName ?? import.meta.url,
|
|
53
|
+
assetName: normalizeAssetName(assetName),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function resolveShowcaseAssetUrl(baseUrlOrAssetName, assetName) {
|
|
58
|
+
const resolved = parseResolveArgs(baseUrlOrAssetName, assetName);
|
|
59
|
+
const fileName = SHOWCASE_ASSET_FILES[resolved.assetName];
|
|
60
|
+
|
|
23
61
|
try {
|
|
24
|
-
return new URL(
|
|
62
|
+
return new URL(`../assets/${fileName}`, resolved.baseUrl);
|
|
25
63
|
} catch {
|
|
26
64
|
const browserBaseUrl = getBrowserBaseUrl();
|
|
27
65
|
if (browserBaseUrl) {
|
|
28
66
|
try {
|
|
29
|
-
const normalizedBaseUrl = new URL(baseUrl, browserBaseUrl);
|
|
30
|
-
return new URL(
|
|
67
|
+
const normalizedBaseUrl = new URL(resolved.baseUrl, browserBaseUrl);
|
|
68
|
+
return new URL(`../assets/${fileName}`, normalizedBaseUrl);
|
|
31
69
|
} catch {
|
|
32
|
-
|
|
70
|
+
const inlineAsset = createInlineShowcaseAssetUrl(resolved.assetName);
|
|
71
|
+
if (inlineAsset) {
|
|
72
|
+
return inlineAsset;
|
|
73
|
+
}
|
|
33
74
|
}
|
|
34
75
|
}
|
|
35
|
-
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
return new URL(`../assets/${fileName}`, import.meta.url);
|
|
79
|
+
} catch {
|
|
80
|
+
return new URL(`assets/${fileName}`, "file:///");
|
|
81
|
+
}
|
|
36
82
|
}
|
|
37
83
|
}
|
|
84
|
+
|
|
85
|
+
export function shouldUseInlineShowcaseFallback(url) {
|
|
86
|
+
const href = url instanceof URL ? url.href : String(url ?? "");
|
|
87
|
+
return href.includes("/assets/");
|
|
88
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const GPU_SHOWCASE_REALISTIC_MODELS_FEATURE = "gpu_showcase_realistic_models_v1";
|
package/src/gltf-loader.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { shouldUseInlineShowcaseFallback } from "./asset-url.js";
|
|
2
|
+
|
|
1
3
|
function decodeDataUri(uri) {
|
|
2
4
|
const match = /^data:.*?;base64,(.+)$/i.exec(uri);
|
|
3
5
|
if (!match) {
|
|
@@ -14,6 +16,8 @@ function decodeDataUri(uri) {
|
|
|
14
16
|
|
|
15
17
|
function getComponentArray(componentType, buffer, byteOffset, count) {
|
|
16
18
|
switch (componentType) {
|
|
19
|
+
case 5121:
|
|
20
|
+
return new Uint8Array(buffer, byteOffset, count);
|
|
17
21
|
case 5123:
|
|
18
22
|
return new Uint16Array(buffer, byteOffset, count);
|
|
19
23
|
case 5125:
|
|
@@ -25,6 +29,17 @@ function getComponentArray(componentType, buffer, byteOffset, count) {
|
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
function getNormalizationScale(componentType) {
|
|
33
|
+
switch (componentType) {
|
|
34
|
+
case 5121:
|
|
35
|
+
return 255;
|
|
36
|
+
case 5123:
|
|
37
|
+
return 65535;
|
|
38
|
+
default:
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
function getTypeSize(type) {
|
|
29
44
|
switch (type) {
|
|
30
45
|
case "SCALAR":
|
|
@@ -41,30 +56,74 @@ function getTypeSize(type) {
|
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
function readAccessor(document, accessorIndex, buffers) {
|
|
44
|
-
const accessor = document.accessors[accessorIndex];
|
|
45
|
-
|
|
59
|
+
const accessor = document.accessors?.[accessorIndex];
|
|
60
|
+
if (!accessor) {
|
|
61
|
+
throw new Error(`glTF accessor ${accessorIndex} is missing.`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const bufferView = document.bufferViews?.[accessor.bufferView];
|
|
65
|
+
if (!bufferView) {
|
|
66
|
+
throw new Error(`glTF bufferView ${accessor.bufferView} is missing.`);
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
const buffer = buffers[bufferView.buffer];
|
|
47
70
|
const componentCount = getTypeSize(accessor.type);
|
|
48
71
|
const byteOffset = (bufferView.byteOffset ?? 0) + (accessor.byteOffset ?? 0);
|
|
49
72
|
const valueCount = accessor.count * componentCount;
|
|
50
|
-
|
|
73
|
+
const values = Array.from(
|
|
74
|
+
getComponentArray(accessor.componentType, buffer, byteOffset, valueCount)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (accessor.normalized) {
|
|
78
|
+
const scale = getNormalizationScale(accessor.componentType);
|
|
79
|
+
return values.map((value) => value / scale);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return values;
|
|
51
83
|
}
|
|
52
84
|
|
|
53
|
-
function
|
|
85
|
+
function getMaterialInfo(document, primitive) {
|
|
54
86
|
const material = document.materials?.[primitive.material] ?? null;
|
|
55
87
|
const factor =
|
|
56
88
|
material?.pbrMetallicRoughness?.baseColorFactor ?? [0.56, 0.33, 0.22, 1];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
const emissive = material?.emissiveFactor ?? [0, 0, 0];
|
|
90
|
+
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
name: material?.name ?? "default-material",
|
|
93
|
+
color: Object.freeze({
|
|
94
|
+
r: factor[0],
|
|
95
|
+
g: factor[1],
|
|
96
|
+
b: factor[2],
|
|
97
|
+
a: factor[3] ?? 1,
|
|
98
|
+
}),
|
|
99
|
+
roughness:
|
|
100
|
+
typeof material?.pbrMetallicRoughness?.roughnessFactor === "number"
|
|
101
|
+
? material.pbrMetallicRoughness.roughnessFactor
|
|
102
|
+
: 0.92,
|
|
103
|
+
metallic:
|
|
104
|
+
typeof material?.pbrMetallicRoughness?.metallicFactor === "number"
|
|
105
|
+
? material.pbrMetallicRoughness.metallicFactor
|
|
106
|
+
: 0.08,
|
|
107
|
+
emissive: Object.freeze({
|
|
108
|
+
r: emissive[0] ?? 0,
|
|
109
|
+
g: emissive[1] ?? 0,
|
|
110
|
+
b: emissive[2] ?? 0,
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
63
113
|
}
|
|
64
114
|
|
|
65
115
|
function computeBounds(positions) {
|
|
66
|
-
const min = [
|
|
67
|
-
|
|
116
|
+
const min = [
|
|
117
|
+
Number.POSITIVE_INFINITY,
|
|
118
|
+
Number.POSITIVE_INFINITY,
|
|
119
|
+
Number.POSITIVE_INFINITY,
|
|
120
|
+
];
|
|
121
|
+
const max = [
|
|
122
|
+
Number.NEGATIVE_INFINITY,
|
|
123
|
+
Number.NEGATIVE_INFINITY,
|
|
124
|
+
Number.NEGATIVE_INFINITY,
|
|
125
|
+
];
|
|
126
|
+
|
|
68
127
|
for (let index = 0; index < positions.length; index += 3) {
|
|
69
128
|
min[0] = Math.min(min[0], positions[index]);
|
|
70
129
|
min[1] = Math.min(min[1], positions[index + 1]);
|
|
@@ -73,11 +132,19 @@ function computeBounds(positions) {
|
|
|
73
132
|
max[1] = Math.max(max[1], positions[index + 1]);
|
|
74
133
|
max[2] = Math.max(max[2], positions[index + 2]);
|
|
75
134
|
}
|
|
76
|
-
|
|
135
|
+
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
min: Object.freeze([min[0], min[1], min[2]]),
|
|
138
|
+
max: Object.freeze([max[0], max[1], max[2]]),
|
|
139
|
+
});
|
|
77
140
|
}
|
|
78
141
|
|
|
79
142
|
function resolveBrowserRequestBaseUrl() {
|
|
80
|
-
if (
|
|
143
|
+
if (
|
|
144
|
+
typeof document !== "undefined" &&
|
|
145
|
+
typeof document.baseURI === "string" &&
|
|
146
|
+
document.baseURI.length > 0
|
|
147
|
+
) {
|
|
81
148
|
return document.baseURI;
|
|
82
149
|
}
|
|
83
150
|
if (
|
|
@@ -106,18 +173,217 @@ function resolveFetchBaseUrl(requestUrl, responseUrl) {
|
|
|
106
173
|
if (browserBaseUrl) {
|
|
107
174
|
return new URL(requestUrl, browserBaseUrl);
|
|
108
175
|
}
|
|
109
|
-
throw new Error(
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Unable to resolve a stable base URL for glTF asset loading: ${String(requestUrl)}`
|
|
178
|
+
);
|
|
110
179
|
}
|
|
111
180
|
}
|
|
112
181
|
|
|
113
|
-
|
|
182
|
+
function createIdentityMatrix() {
|
|
183
|
+
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function multiplyMatrices(a, b) {
|
|
187
|
+
const out = new Array(16).fill(0);
|
|
188
|
+
for (let column = 0; column < 4; column += 1) {
|
|
189
|
+
for (let row = 0; row < 4; row += 1) {
|
|
190
|
+
out[column * 4 + row] =
|
|
191
|
+
a[0 * 4 + row] * b[column * 4 + 0] +
|
|
192
|
+
a[1 * 4 + row] * b[column * 4 + 1] +
|
|
193
|
+
a[2 * 4 + row] * b[column * 4 + 2] +
|
|
194
|
+
a[3 * 4 + row] * b[column * 4 + 3];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function composeNodeMatrix(node) {
|
|
201
|
+
if (Array.isArray(node.matrix) && node.matrix.length === 16) {
|
|
202
|
+
return [...node.matrix];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const translation = Array.isArray(node.translation) ? node.translation : [0, 0, 0];
|
|
206
|
+
const rotation = Array.isArray(node.rotation) ? node.rotation : [0, 0, 0, 1];
|
|
207
|
+
const scale = Array.isArray(node.scale) ? node.scale : [1, 1, 1];
|
|
208
|
+
const [x, y, z, w] = rotation;
|
|
209
|
+
const x2 = x + x;
|
|
210
|
+
const y2 = y + y;
|
|
211
|
+
const z2 = z + z;
|
|
212
|
+
const xx = x * x2;
|
|
213
|
+
const xy = x * y2;
|
|
214
|
+
const xz = x * z2;
|
|
215
|
+
const yy = y * y2;
|
|
216
|
+
const yz = y * z2;
|
|
217
|
+
const zz = z * z2;
|
|
218
|
+
const wx = w * x2;
|
|
219
|
+
const wy = w * y2;
|
|
220
|
+
const wz = w * z2;
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
(1 - (yy + zz)) * scale[0],
|
|
224
|
+
(xy + wz) * scale[0],
|
|
225
|
+
(xz - wy) * scale[0],
|
|
226
|
+
0,
|
|
227
|
+
(xy - wz) * scale[1],
|
|
228
|
+
(1 - (xx + zz)) * scale[1],
|
|
229
|
+
(yz + wx) * scale[1],
|
|
230
|
+
0,
|
|
231
|
+
(xz + wy) * scale[2],
|
|
232
|
+
(yz - wx) * scale[2],
|
|
233
|
+
(1 - (xx + yy)) * scale[2],
|
|
234
|
+
0,
|
|
235
|
+
translation[0],
|
|
236
|
+
translation[1],
|
|
237
|
+
translation[2],
|
|
238
|
+
1,
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function transformPosition(position, matrix) {
|
|
243
|
+
return [
|
|
244
|
+
matrix[0] * position[0] + matrix[4] * position[1] + matrix[8] * position[2] + matrix[12],
|
|
245
|
+
matrix[1] * position[0] + matrix[5] * position[1] + matrix[9] * position[2] + matrix[13],
|
|
246
|
+
matrix[2] * position[0] + matrix[6] * position[1] + matrix[10] * position[2] + matrix[14],
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function transformNormal(normal, matrix) {
|
|
251
|
+
const transformed = [
|
|
252
|
+
matrix[0] * normal[0] + matrix[4] * normal[1] + matrix[8] * normal[2],
|
|
253
|
+
matrix[1] * normal[0] + matrix[5] * normal[1] + matrix[9] * normal[2],
|
|
254
|
+
matrix[2] * normal[0] + matrix[6] * normal[1] + matrix[10] * normal[2],
|
|
255
|
+
];
|
|
256
|
+
const length = Math.hypot(transformed[0], transformed[1], transformed[2]) || 1;
|
|
257
|
+
return [transformed[0] / length, transformed[1] / length, transformed[2] / length];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function collectScenePrimitives(document, buffers) {
|
|
261
|
+
const scene = document.scenes?.[document.scene ?? 0];
|
|
262
|
+
if (!scene || !Array.isArray(scene.nodes) || scene.nodes.length === 0) {
|
|
263
|
+
throw new Error("glTF demo asset must expose a default scene with at least one node.");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const results = [];
|
|
267
|
+
let modelName = null;
|
|
268
|
+
let physics = null;
|
|
269
|
+
|
|
270
|
+
function visit(nodeIndex, parentMatrix) {
|
|
271
|
+
const node = document.nodes?.[nodeIndex];
|
|
272
|
+
if (!node) {
|
|
273
|
+
throw new Error(`glTF node ${nodeIndex} is missing.`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const localMatrix = composeNodeMatrix(node);
|
|
277
|
+
const worldMatrix = multiplyMatrices(parentMatrix, localMatrix);
|
|
278
|
+
|
|
279
|
+
if (!modelName && typeof node.name === "string" && node.name.length > 0) {
|
|
280
|
+
modelName = node.name;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!physics && node.extras?.physics && typeof node.extras.physics === "object") {
|
|
284
|
+
physics = Object.freeze({ ...node.extras.physics });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (typeof node.mesh === "number") {
|
|
288
|
+
const mesh = document.meshes?.[node.mesh];
|
|
289
|
+
if (!mesh || !Array.isArray(mesh.primitives)) {
|
|
290
|
+
throw new Error(`glTF mesh ${node.mesh} is missing primitives.`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
mesh.primitives.forEach((primitive, primitiveIndex) => {
|
|
294
|
+
const positions = readAccessor(document, primitive.attributes.POSITION, buffers);
|
|
295
|
+
const normals =
|
|
296
|
+
typeof primitive.attributes.NORMAL === "number"
|
|
297
|
+
? readAccessor(document, primitive.attributes.NORMAL, buffers)
|
|
298
|
+
: null;
|
|
299
|
+
const colors =
|
|
300
|
+
typeof primitive.attributes.COLOR_0 === "number"
|
|
301
|
+
? readAccessor(document, primitive.attributes.COLOR_0, buffers)
|
|
302
|
+
: null;
|
|
303
|
+
const transformedPositions = [];
|
|
304
|
+
const transformedNormals = [];
|
|
305
|
+
|
|
306
|
+
for (let index = 0; index < positions.length; index += 3) {
|
|
307
|
+
const point = transformPosition(
|
|
308
|
+
[positions[index], positions[index + 1], positions[index + 2]],
|
|
309
|
+
worldMatrix
|
|
310
|
+
);
|
|
311
|
+
transformedPositions.push(point[0], point[1], point[2]);
|
|
312
|
+
|
|
313
|
+
if (normals) {
|
|
314
|
+
const normal = transformNormal(
|
|
315
|
+
[normals[index], normals[index + 1], normals[index + 2]],
|
|
316
|
+
worldMatrix
|
|
317
|
+
);
|
|
318
|
+
transformedNormals.push(normal[0], normal[1], normal[2]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const indices =
|
|
323
|
+
typeof primitive.indices === "number"
|
|
324
|
+
? readAccessor(document, primitive.indices, buffers).map((value) => Number(value))
|
|
325
|
+
: Array.from({ length: transformedPositions.length / 3 }, (_, index) => index);
|
|
326
|
+
const material = getMaterialInfo(document, primitive);
|
|
327
|
+
const primitiveName =
|
|
328
|
+
`${node.name ?? mesh.name ?? "mesh"}-${primitiveIndex}`;
|
|
329
|
+
|
|
330
|
+
results.push(
|
|
331
|
+
Object.freeze({
|
|
332
|
+
name: primitiveName,
|
|
333
|
+
positions: Object.freeze(transformedPositions),
|
|
334
|
+
indices: Object.freeze(indices),
|
|
335
|
+
normals:
|
|
336
|
+
transformedNormals.length > 0
|
|
337
|
+
? Object.freeze(transformedNormals)
|
|
338
|
+
: null,
|
|
339
|
+
colors: colors ? Object.freeze(colors) : null,
|
|
340
|
+
material,
|
|
341
|
+
bounds: computeBounds(transformedPositions),
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (Array.isArray(node.children)) {
|
|
348
|
+
for (const childIndex of node.children) {
|
|
349
|
+
visit(childIndex, worldMatrix);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for (const rootNodeIndex of scene.nodes) {
|
|
355
|
+
visit(rootNodeIndex, createIdentityMatrix());
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (results.length === 0) {
|
|
359
|
+
throw new Error("glTF demo asset must contain at least one mesh primitive.");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
name: modelName ?? "gltf-model",
|
|
364
|
+
physics: physics ?? Object.freeze({}),
|
|
365
|
+
primitives: results,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function loadGltfDocument(url) {
|
|
114
370
|
const response = await fetch(url);
|
|
115
371
|
if (!response.ok) {
|
|
116
372
|
throw new Error(`Failed to load glTF asset: ${response.status} ${response.statusText}`);
|
|
117
373
|
}
|
|
118
374
|
|
|
119
|
-
|
|
120
|
-
|
|
375
|
+
return {
|
|
376
|
+
document: await response.json(),
|
|
377
|
+
baseUrl: resolveFetchBaseUrl(url, response.url),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function loadInlineShowcaseDocument() {
|
|
382
|
+
const module = await import("./showcase-inline-assets.js");
|
|
383
|
+
return loadGltfDocument(new URL(module.INLINE_SHOWCASE_ASSET_URLS.brigantine));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function buildGltfModel(document, baseUrl) {
|
|
121
387
|
const buffers = await Promise.all(
|
|
122
388
|
(document.buffers ?? []).map(async (buffer) => {
|
|
123
389
|
if (typeof buffer.uri !== "string") {
|
|
@@ -134,23 +400,47 @@ export async function loadGltfModel(url) {
|
|
|
134
400
|
})
|
|
135
401
|
);
|
|
136
402
|
|
|
137
|
-
const scene = document
|
|
138
|
-
|
|
139
|
-
|
|
403
|
+
const scene = collectScenePrimitives(document, buffers);
|
|
404
|
+
const aggregatePositions = [];
|
|
405
|
+
const aggregateIndices = [];
|
|
406
|
+
|
|
407
|
+
for (const primitive of scene.primitives) {
|
|
408
|
+
const vertexOffset = aggregatePositions.length / 3;
|
|
409
|
+
aggregatePositions.push(...primitive.positions);
|
|
410
|
+
aggregateIndices.push(...primitive.indices.map((index) => index + vertexOffset));
|
|
140
411
|
}
|
|
141
412
|
|
|
142
|
-
const
|
|
143
|
-
const mesh = document.meshes[node.mesh];
|
|
144
|
-
const primitive = mesh.primitives[0];
|
|
145
|
-
const positions = Array.from(readAccessor(document, primitive.attributes.POSITION, buffers));
|
|
146
|
-
const indices = Array.from(readAccessor(document, primitive.indices, buffers));
|
|
413
|
+
const color = scene.primitives[0]?.material?.color ?? { r: 0.56, g: 0.33, b: 0.22, a: 1 };
|
|
147
414
|
|
|
148
415
|
return Object.freeze({
|
|
149
|
-
name:
|
|
150
|
-
positions,
|
|
151
|
-
indices,
|
|
152
|
-
bounds: computeBounds(
|
|
153
|
-
color:
|
|
154
|
-
physics:
|
|
416
|
+
name: scene.name,
|
|
417
|
+
positions: Object.freeze(aggregatePositions),
|
|
418
|
+
indices: Object.freeze(aggregateIndices),
|
|
419
|
+
bounds: computeBounds(aggregatePositions),
|
|
420
|
+
color: Object.freeze({ ...color }),
|
|
421
|
+
physics: scene.physics,
|
|
422
|
+
primitives: Object.freeze(scene.primitives),
|
|
155
423
|
});
|
|
156
424
|
}
|
|
425
|
+
|
|
426
|
+
function shouldRetryWithInlineShowcaseFallback(url, error) {
|
|
427
|
+
if (!shouldUseInlineShowcaseFallback(url)) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return error instanceof TypeError || /^Failed to load glTF asset:/u.test(error.message);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function loadGltfModel(url) {
|
|
435
|
+
try {
|
|
436
|
+
const { document, baseUrl } = await loadGltfDocument(url);
|
|
437
|
+
return buildGltfModel(document, baseUrl);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (!shouldRetryWithInlineShowcaseFallback(url, error)) {
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const { document, baseUrl } = await loadInlineShowcaseDocument();
|
|
444
|
+
return buildGltfModel(document, baseUrl);
|
|
445
|
+
}
|
|
446
|
+
}
|
package/src/i18n.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { gpuSharedEnGbTranslations } from "./translations/en-GB.js";
|
|
2
|
+
|
|
3
|
+
export const gpuSharedTranslationKeys = Object.freeze({
|
|
4
|
+
showcaseTitle: "gpuShared.showcase.title",
|
|
5
|
+
showcaseSubtitle: "gpuShared.showcase.subtitle",
|
|
6
|
+
statusBooting: "gpuShared.showcase.status.booting",
|
|
7
|
+
statusLive: "gpuShared.showcase.status.live",
|
|
8
|
+
detailsBooting: "gpuShared.showcase.details.booting",
|
|
9
|
+
detailsPhysics: "gpuShared.showcase.details.physics",
|
|
10
|
+
detailsRealistic: "gpuShared.showcase.details.realistic",
|
|
11
|
+
detailsLegacy: "gpuShared.showcase.details.legacy",
|
|
12
|
+
pause: "gpuShared.showcase.action.pause",
|
|
13
|
+
resume: "gpuShared.showcase.action.resume",
|
|
14
|
+
stressMode: "gpuShared.showcase.control.stressMode",
|
|
15
|
+
focus: "gpuShared.showcase.control.focus",
|
|
16
|
+
focusIntegrated: "gpuShared.showcase.focus.integrated",
|
|
17
|
+
focusLighting: "gpuShared.showcase.focus.lighting",
|
|
18
|
+
focusCloth: "gpuShared.showcase.focus.cloth",
|
|
19
|
+
focusFluid: "gpuShared.showcase.focus.fluid",
|
|
20
|
+
focusPhysics: "gpuShared.showcase.focus.physics",
|
|
21
|
+
focusPerformance: "gpuShared.showcase.focus.performance",
|
|
22
|
+
focusDebug: "gpuShared.showcase.focus.debug",
|
|
23
|
+
legendTitle: "gpuShared.showcase.legend.title",
|
|
24
|
+
legendShipMetadata: "gpuShared.showcase.legend.shipMetadata",
|
|
25
|
+
legendLighting: "gpuShared.showcase.legend.lighting",
|
|
26
|
+
legendCollisions: "gpuShared.showcase.legend.collisions",
|
|
27
|
+
sceneState: "gpuShared.showcase.section.sceneState",
|
|
28
|
+
qualityBudgets: "gpuShared.showcase.section.qualityBudgets",
|
|
29
|
+
debugTelemetry: "gpuShared.showcase.section.debugTelemetry",
|
|
30
|
+
notes: "gpuShared.showcase.section.notes",
|
|
31
|
+
noteAssetLoading: "gpuShared.showcase.note.assetLoading",
|
|
32
|
+
noteMoonlight: "gpuShared.showcase.note.moonlight",
|
|
33
|
+
noteContinuity: "gpuShared.showcase.note.continuity",
|
|
34
|
+
notePerformance: "gpuShared.showcase.note.performance",
|
|
35
|
+
notePhysicsSnapshots: "gpuShared.showcase.note.physicsSnapshots",
|
|
36
|
+
notePhysicsCollisions: "gpuShared.showcase.note.physicsCollisions",
|
|
37
|
+
notePhysicsLighting: "gpuShared.showcase.note.physicsLighting",
|
|
38
|
+
debugAdapterShowcase: "gpuShared.debug.adapter.showcase",
|
|
39
|
+
debugMainColorBuffer: "gpuShared.debug.allocation.mainColorBuffer",
|
|
40
|
+
debugShadowImpressionAtlas: "gpuShared.debug.allocation.shadowImpressionAtlas",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const gpuSharedTranslations = Object.freeze({
|
|
44
|
+
"en-GB": gpuSharedEnGbTranslations,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
function formatTranslation(template, args = {}) {
|
|
48
|
+
return template.replace(/\{([A-Za-z0-9_]+)\}/g, (match, name) => {
|
|
49
|
+
if (!Object.prototype.hasOwnProperty.call(args, name)) {
|
|
50
|
+
return match;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const value = args[name];
|
|
54
|
+
return value == null ? "" : String(value);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function translateGpuSharedText(key, args, translate) {
|
|
59
|
+
const translated = translate?.(key, args);
|
|
60
|
+
if (translated && translated !== key) {
|
|
61
|
+
return translated;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const fallback = gpuSharedEnGbTranslations[key];
|
|
65
|
+
return fallback ? formatTranslation(fallback, args) : key;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createGpuSharedTranslator(translate) {
|
|
69
|
+
return (key, args) => translateGpuSharedText(key, args, translate);
|
|
70
|
+
}
|
|
71
|
+
|