@oxy.so/bloom 7.1.2 → 7.1.4
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/assets/character-runtime/README.md +114 -19
- package/assets/character-runtime/authored-parts.mjs +754 -0
- package/assets/character-runtime/catalog-parts.mjs +362 -0
- package/assets/character-runtime/character-recipe.mjs +247 -0
- package/assets/character-runtime/clippo-geometry.mjs +679 -0
- package/assets/character-runtime/controller-mode.mjs +208 -0
- package/assets/character-runtime/cyclops-geometry.mjs +180 -0
- package/assets/character-runtime/legacy-engine.mjs +406 -54
- package/assets/character-runtime/legacy-geometry.mjs +242 -85
- package/assets/character-runtime/legacy-worker.mjs +82 -23
- package/assets/character-runtime/managed-avatar.mjs +12 -17
- package/assets/character-runtime/migrated-contours.mjs +2067 -0
- package/assets/character-runtime/migrated-faces.mjs +59 -0
- package/assets/character-runtime/pose-transition.mjs +523 -0
- package/assets/character-runtime/preparation-cache.mjs +60 -0
- package/assets/character-runtime/render-budget.mjs +5 -3
- package/assets/character-runtime/render-errors.mjs +180 -0
- package/assets/character-runtime/resolution.mjs +10 -0
- package/assets/character-runtime/runtime.mjs +744 -295
- package/assets/character-runtime/sha256.mjs +44 -0
- package/assets/character-runtime/shared-programs.mjs +534 -0
- package/assets/character-runtime/shared-surface.mjs +490 -0
- package/assets/character-runtime/shared-textures.mjs +420 -0
- package/assets/character-runtime/thumbnails/eyes/todd.png +0 -0
- package/docs/agent-avatar.mdx +45 -8
- package/docs/agent-creator.mdx +13 -3
- package/lib/commonjs/agent-avatar/AgentAvatar.js +1 -1
- package/lib/commonjs/agent-avatar/AgentAvatar.js.map +1 -1
- package/lib/commonjs/agent-avatar/CharacterAvatar.js +2 -1
- package/lib/commonjs/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/commonjs/agent-avatar/character-colors.js +19 -0
- package/lib/commonjs/agent-avatar/character-colors.js.map +1 -0
- package/lib/commonjs/agent-avatar/character-shapes.js +106 -0
- package/lib/commonjs/agent-avatar/character-shapes.js.map +1 -0
- package/lib/commonjs/agent-avatar/config-character.js +4 -0
- package/lib/commonjs/agent-avatar/config-character.js.map +1 -1
- package/lib/commonjs/agent-avatar/legacy-recipe.js +19 -6
- package/lib/commonjs/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/commonjs/agent-creator/AgentCreatorBase.js +67 -107
- package/lib/commonjs/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/commonjs/agent-creator/CharacterControls.js +23 -5
- package/lib/commonjs/agent-creator/CharacterControls.js.map +1 -1
- package/lib/commonjs/agent-creator/ClippoArtwork.js +63 -0
- package/lib/commonjs/agent-creator/ClippoArtwork.js.map +1 -0
- package/lib/commonjs/agent-creator/CyclopsArtwork.js +45 -0
- package/lib/commonjs/agent-creator/CyclopsArtwork.js.map +1 -0
- package/lib/commonjs/agent-creator/EmotionPicker.js +8 -1
- package/lib/commonjs/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/commonjs/agent-creator/ShapeSilhouette.js +8 -1
- package/lib/commonjs/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/commonjs/agent-creator/ToddArtwork.js +29 -0
- package/lib/commonjs/agent-creator/ToddArtwork.js.map +1 -0
- package/lib/commonjs/agent-creator/constants.js +16 -17
- package/lib/commonjs/agent-creator/constants.js.map +1 -1
- package/lib/commonjs/agent-creator/messages.js +1 -1
- package/lib/commonjs/agent-creator/messages.js.map +1 -1
- package/lib/commonjs/locale/translations/ar.js +1 -1
- package/lib/commonjs/locale/translations/ar.js.map +1 -1
- package/lib/commonjs/locale/translations/bn.js +1 -1
- package/lib/commonjs/locale/translations/bn.js.map +1 -1
- package/lib/commonjs/locale/translations/ca.js +1 -1
- package/lib/commonjs/locale/translations/ca.js.map +1 -1
- package/lib/commonjs/locale/translations/de.js +1 -1
- package/lib/commonjs/locale/translations/de.js.map +1 -1
- package/lib/commonjs/locale/translations/es.js +1 -1
- package/lib/commonjs/locale/translations/es.js.map +1 -1
- package/lib/commonjs/locale/translations/fr.js +1 -1
- package/lib/commonjs/locale/translations/fr.js.map +1 -1
- package/lib/commonjs/locale/translations/hi.js +1 -1
- package/lib/commonjs/locale/translations/hi.js.map +1 -1
- package/lib/commonjs/locale/translations/id.js +1 -1
- package/lib/commonjs/locale/translations/id.js.map +1 -1
- package/lib/commonjs/locale/translations/it.js +1 -1
- package/lib/commonjs/locale/translations/it.js.map +1 -1
- package/lib/commonjs/locale/translations/ja.js +1 -1
- package/lib/commonjs/locale/translations/ja.js.map +1 -1
- package/lib/commonjs/locale/translations/pt.js +1 -1
- package/lib/commonjs/locale/translations/pt.js.map +1 -1
- package/lib/commonjs/locale/translations/ru.js +1 -1
- package/lib/commonjs/locale/translations/ru.js.map +1 -1
- package/lib/commonjs/locale/translations/tr.js +1 -1
- package/lib/commonjs/locale/translations/tr.js.map +1 -1
- package/lib/commonjs/locale/translations/zh.js +1 -1
- package/lib/commonjs/locale/translations/zh.js.map +1 -1
- package/lib/module/agent-avatar/AgentAvatar.js +1 -1
- package/lib/module/agent-avatar/AgentAvatar.js.map +1 -1
- package/lib/module/agent-avatar/CharacterAvatar.js +2 -1
- package/lib/module/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/module/agent-avatar/character-colors.js +15 -0
- package/lib/module/agent-avatar/character-colors.js.map +1 -0
- package/lib/module/agent-avatar/character-shapes.js +97 -0
- package/lib/module/agent-avatar/character-shapes.js.map +1 -0
- package/lib/module/agent-avatar/config-character.js +4 -0
- package/lib/module/agent-avatar/config-character.js.map +1 -1
- package/lib/module/agent-avatar/legacy-recipe.js +19 -6
- package/lib/module/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/module/agent-creator/AgentCreatorBase.js +69 -109
- package/lib/module/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/module/agent-creator/CharacterControls.js +24 -6
- package/lib/module/agent-creator/CharacterControls.js.map +1 -1
- package/lib/module/agent-creator/ClippoArtwork.js +59 -0
- package/lib/module/agent-creator/ClippoArtwork.js.map +1 -0
- package/lib/module/agent-creator/CyclopsArtwork.js +41 -0
- package/lib/module/agent-creator/CyclopsArtwork.js.map +1 -0
- package/lib/module/agent-creator/EmotionPicker.js +8 -1
- package/lib/module/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/module/agent-creator/ShapeSilhouette.js +8 -1
- package/lib/module/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/module/agent-creator/ToddArtwork.js +25 -0
- package/lib/module/agent-creator/ToddArtwork.js.map +1 -0
- package/lib/module/agent-creator/constants.js +8 -16
- package/lib/module/agent-creator/constants.js.map +1 -1
- package/lib/module/agent-creator/messages.js +1 -1
- package/lib/module/agent-creator/messages.js.map +1 -1
- package/lib/module/locale/translations/ar.js +1 -1
- package/lib/module/locale/translations/ar.js.map +1 -1
- package/lib/module/locale/translations/bn.js +1 -1
- package/lib/module/locale/translations/bn.js.map +1 -1
- package/lib/module/locale/translations/ca.js +1 -1
- package/lib/module/locale/translations/ca.js.map +1 -1
- package/lib/module/locale/translations/de.js +1 -1
- package/lib/module/locale/translations/de.js.map +1 -1
- package/lib/module/locale/translations/es.js +1 -1
- package/lib/module/locale/translations/es.js.map +1 -1
- package/lib/module/locale/translations/fr.js +1 -1
- package/lib/module/locale/translations/fr.js.map +1 -1
- package/lib/module/locale/translations/hi.js +1 -1
- package/lib/module/locale/translations/hi.js.map +1 -1
- package/lib/module/locale/translations/id.js +1 -1
- package/lib/module/locale/translations/id.js.map +1 -1
- package/lib/module/locale/translations/it.js +1 -1
- package/lib/module/locale/translations/it.js.map +1 -1
- package/lib/module/locale/translations/ja.js +1 -1
- package/lib/module/locale/translations/ja.js.map +1 -1
- package/lib/module/locale/translations/pt.js +1 -1
- package/lib/module/locale/translations/pt.js.map +1 -1
- package/lib/module/locale/translations/ru.js +1 -1
- package/lib/module/locale/translations/ru.js.map +1 -1
- package/lib/module/locale/translations/tr.js +1 -1
- package/lib/module/locale/translations/tr.js.map +1 -1
- package/lib/module/locale/translations/zh.js +1 -1
- package/lib/module/locale/translations/zh.js.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/CharacterAvatar.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts +13 -0
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts +19 -0
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-avatar/config-character.d.ts +4 -0
- package/lib/typescript/commonjs/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts +2 -10
- package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/AgentCreatorBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts +2 -1
- package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts +6 -0
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts +5 -0
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts +1 -0
- package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/ShapeSilhouette.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts +6 -0
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts.map +1 -0
- package/lib/typescript/commonjs/agent-creator/constants.d.ts +7 -15
- package/lib/typescript/commonjs/agent-creator/constants.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/messages.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/CharacterAvatar.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/character-colors.d.ts +13 -0
- package/lib/typescript/module/agent-avatar/character-colors.d.ts.map +1 -0
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts +19 -0
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts.map +1 -0
- package/lib/typescript/module/agent-avatar/config-character.d.ts +4 -0
- package/lib/typescript/module/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts +2 -10
- package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/AgentCreatorBase.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/CharacterControls.d.ts +2 -1
- package/lib/typescript/module/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts +6 -0
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts.map +1 -0
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts +5 -0
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts.map +1 -0
- package/lib/typescript/module/agent-creator/EmotionPicker.d.ts +1 -0
- package/lib/typescript/module/agent-creator/EmotionPicker.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/ShapeSilhouette.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts +6 -0
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts.map +1 -0
- package/lib/typescript/module/agent-creator/constants.d.ts +7 -15
- package/lib/typescript/module/agent-creator/constants.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/messages.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent-avatar/AgentAvatar.tsx +1 -1
- package/src/agent-avatar/CharacterAvatar.tsx +4 -1
- package/src/agent-avatar/character-colors.ts +12 -0
- package/src/agent-avatar/character-shapes.ts +122 -0
- package/src/agent-avatar/config-character.ts +39 -6
- package/src/agent-avatar/legacy-recipe.ts +34 -6
- package/src/agent-creator/AgentCreatorBase.tsx +121 -152
- package/src/agent-creator/CharacterControls.tsx +39 -14
- package/src/agent-creator/ClippoArtwork.tsx +49 -0
- package/src/agent-creator/CyclopsArtwork.tsx +27 -0
- package/src/agent-creator/EmotionPicker.tsx +55 -16
- package/src/agent-creator/ShapeSilhouette.tsx +16 -1
- package/src/agent-creator/ToddArtwork.tsx +24 -0
- package/src/agent-creator/constants.ts +47 -14
- package/src/agent-creator/messages.ts +2 -1
- package/src/locale/translations/ar.ts +1 -1
- package/src/locale/translations/bn.ts +1 -1
- package/src/locale/translations/ca.ts +1 -1
- package/src/locale/translations/de.ts +1 -1
- package/src/locale/translations/es.ts +1 -1
- package/src/locale/translations/fr.ts +1 -1
- package/src/locale/translations/hi.ts +1 -1
- package/src/locale/translations/id.ts +1 -1
- package/src/locale/translations/it.ts +1 -1
- package/src/locale/translations/ja.ts +1 -1
- package/src/locale/translations/pt.ts +1 -1
- package/src/locale/translations/ru.ts +1 -1
- package/src/locale/translations/tr.ts +1 -1
- package/src/locale/translations/zh.ts +1 -1
- package/src/__tests__/support/adoption-matrix.ts +0 -683
- package/src/__tests__/support/card-surface.ts +0 -25
- package/src/__tests__/support/collision-fixture-barrel.ts +0 -20
- package/src/__tests__/support/commerce-harness.tsx +0 -97
- package/src/__tests__/support/composite.ts +0 -68
- package/src/__tests__/support/constructed-style-sheets.ts +0 -68
- package/src/__tests__/support/messages-in.ts +0 -10
- package/src/__tests__/support/press-host.ts +0 -30
- package/src/__tests__/support/rendered-style.ts +0 -99
- package/src/__tests__/support/unread-hook-fixture.ts +0 -33
- package/src/__tests__/support/worklet-capture-fixture.tsx +0 -28
- package/src/agent-avatar/__tests__/artwork-golden.json +0 -774
- package/src/agent-creator/__tests__/support/character-runtime-fixture.tsx +0 -26
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +0 -9346
- package/src/theme/__tests__/__fixtures__/tonal-glass-primary-failures.json +0 -69
- package/src/theme/__tests__/fixtures/color-engine-golden.json +0 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Generated from shape-owned source face geometry; regenerate with scripts/generate-avatar-contours.mjs.
|
|
2
|
+
export const MIGRATED_FACES = Object.freeze({
|
|
3
|
+
slender: {
|
|
4
|
+
centers: [
|
|
5
|
+
[-0.3419277108433735, 0.04819277108433735],
|
|
6
|
+
[0.09325301204819275, 0.04819277108433735],
|
|
7
|
+
],
|
|
8
|
+
radius: 0.1740722891566265,
|
|
9
|
+
},
|
|
10
|
+
pocket: {
|
|
11
|
+
centers: [
|
|
12
|
+
[-0.3975903614457831, 0.04819277108433735],
|
|
13
|
+
[0.10843373493975902, 0.04819277108433735],
|
|
14
|
+
],
|
|
15
|
+
radius: 0.20240963855421687,
|
|
16
|
+
},
|
|
17
|
+
petal: {
|
|
18
|
+
centers: [
|
|
19
|
+
[-0.3012048192771084, 0.04819277108433735],
|
|
20
|
+
[0.20481927710843373, 0.04819277108433735],
|
|
21
|
+
],
|
|
22
|
+
radius: 0.20240963855421687,
|
|
23
|
+
},
|
|
24
|
+
star: {
|
|
25
|
+
centers: [
|
|
26
|
+
[-0.3253012048192771, -0.024096385542168676],
|
|
27
|
+
[0.18072289156626503, -0.024096385542168676],
|
|
28
|
+
],
|
|
29
|
+
radius: 0.20240963855421687,
|
|
30
|
+
},
|
|
31
|
+
cloud: {
|
|
32
|
+
centers: [
|
|
33
|
+
[-0.3253012048192771, -0.0963855421686747],
|
|
34
|
+
[0.18072289156626503, -0.0963855421686747],
|
|
35
|
+
],
|
|
36
|
+
radius: 0.20240963855421687,
|
|
37
|
+
},
|
|
38
|
+
shield: {
|
|
39
|
+
centers: [
|
|
40
|
+
[-0.3253012048192771, 0.04819277108433735],
|
|
41
|
+
[0.18072289156626503, 0.04819277108433735],
|
|
42
|
+
],
|
|
43
|
+
radius: 0.20240963855421687,
|
|
44
|
+
},
|
|
45
|
+
pebble: {
|
|
46
|
+
centers: [
|
|
47
|
+
[-0.24096385542168675, 0],
|
|
48
|
+
[0.24096385542168675, 0],
|
|
49
|
+
],
|
|
50
|
+
radius: 0.1927710843373494,
|
|
51
|
+
},
|
|
52
|
+
squircle: {
|
|
53
|
+
centers: [
|
|
54
|
+
[-0.24096385542168675, 0],
|
|
55
|
+
[0.24096385542168675, 0],
|
|
56
|
+
],
|
|
57
|
+
radius: 0.1927710843373494,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
// Blend only the original engine's reflected pose blocks. Vertex/index buffers,
|
|
2
|
+
// material values and shader switches are never rewritten by this adapter.
|
|
3
|
+
const identity = () =>
|
|
4
|
+
new Float64Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
5
|
+
export function multiplyMatrix(a, b) {
|
|
6
|
+
const out = new Float64Array(16);
|
|
7
|
+
for (let c = 0; c < 4; c++)
|
|
8
|
+
for (let r = 0; r < 4; r++)
|
|
9
|
+
for (let k = 0; k < 4; k++) out[c * 4 + r] += a[k * 4 + r] * b[c * 4 + k];
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
export function inverseMatrix(m) {
|
|
13
|
+
const a = Array.from({ length: 4 }, (_, r) =>
|
|
14
|
+
Array.from({ length: 8 }, (_, c) =>
|
|
15
|
+
c < 4 ? m[c * 4 + r] : +(c - 4 === r),
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
for (let c = 0; c < 4; c++) {
|
|
19
|
+
let pivot = c;
|
|
20
|
+
for (let r = c + 1; r < 4; r++)
|
|
21
|
+
if (Math.abs(a[r][c]) > Math.abs(a[pivot][c])) pivot = r;
|
|
22
|
+
if (Math.abs(a[pivot][c]) < 1e-10) return null;
|
|
23
|
+
[a[c], a[pivot]] = [a[pivot], a[c]];
|
|
24
|
+
const divisor = a[c][c];
|
|
25
|
+
for (let k = 0; k < 8; k++) a[c][k] /= divisor;
|
|
26
|
+
for (let r = 0; r < 4; r++)
|
|
27
|
+
if (r !== c) {
|
|
28
|
+
const f = a[r][c];
|
|
29
|
+
for (let k = 0; k < 8; k++) a[r][k] -= f * a[c][k];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return Float64Array.from(
|
|
33
|
+
{ length: 16 },
|
|
34
|
+
(_, i) => a[i % 4][4 + Math.floor(i / 4)],
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function dot(a, b) {
|
|
38
|
+
return a.reduce((s, x, i) => s + x * b[i], 0);
|
|
39
|
+
}
|
|
40
|
+
function normalized(a) {
|
|
41
|
+
const n = Math.hypot(...a);
|
|
42
|
+
return n > 1e-10 ? a.map((x) => x / n) : null;
|
|
43
|
+
}
|
|
44
|
+
function quaternion(m) {
|
|
45
|
+
const t = m[0] + m[5] + m[10];
|
|
46
|
+
let x, y, z, w, s;
|
|
47
|
+
if (t > 0) {
|
|
48
|
+
s = Math.sqrt(t + 1) * 2;
|
|
49
|
+
w = s / 4;
|
|
50
|
+
x = (m[6] - m[9]) / s;
|
|
51
|
+
y = (m[8] - m[2]) / s;
|
|
52
|
+
z = (m[1] - m[4]) / s;
|
|
53
|
+
} else if (m[0] > m[5] && m[0] > m[10]) {
|
|
54
|
+
s = Math.sqrt(1 + m[0] - m[5] - m[10]) * 2;
|
|
55
|
+
w = (m[6] - m[9]) / s;
|
|
56
|
+
x = s / 4;
|
|
57
|
+
y = (m[4] + m[1]) / s;
|
|
58
|
+
z = (m[8] + m[2]) / s;
|
|
59
|
+
} else if (m[5] > m[10]) {
|
|
60
|
+
s = Math.sqrt(1 + m[5] - m[0] - m[10]) * 2;
|
|
61
|
+
w = (m[8] - m[2]) / s;
|
|
62
|
+
x = (m[4] + m[1]) / s;
|
|
63
|
+
y = s / 4;
|
|
64
|
+
z = (m[9] + m[6]) / s;
|
|
65
|
+
} else {
|
|
66
|
+
s = Math.sqrt(1 + m[10] - m[0] - m[5]) * 2;
|
|
67
|
+
w = (m[1] - m[4]) / s;
|
|
68
|
+
x = (m[8] + m[2]) / s;
|
|
69
|
+
y = (m[9] + m[6]) / s;
|
|
70
|
+
z = s / 4;
|
|
71
|
+
}
|
|
72
|
+
return normalized([x, y, z, w]);
|
|
73
|
+
}
|
|
74
|
+
function decompose(m) {
|
|
75
|
+
if (
|
|
76
|
+
!Array.from(m).every(Number.isFinite) ||
|
|
77
|
+
Math.abs(m[3]) + Math.abs(m[7]) + Math.abs(m[11]) + Math.abs(m[15] - 1) >
|
|
78
|
+
1e-5
|
|
79
|
+
)
|
|
80
|
+
return null;
|
|
81
|
+
const scales = [
|
|
82
|
+
Math.hypot(m[0], m[1], m[2]),
|
|
83
|
+
Math.hypot(m[4], m[5], m[6]),
|
|
84
|
+
Math.hypot(m[8], m[9], m[10]),
|
|
85
|
+
];
|
|
86
|
+
if (scales.some((x) => x < 1e-8)) return null;
|
|
87
|
+
const determinant =
|
|
88
|
+
m[0] * (m[5] * m[10] - m[6] * m[9]) -
|
|
89
|
+
m[4] * (m[1] * m[10] - m[2] * m[9]) +
|
|
90
|
+
m[8] * (m[1] * m[6] - m[2] * m[5]);
|
|
91
|
+
if (determinant < 0) scales[0] *= -1;
|
|
92
|
+
const rotation = identity();
|
|
93
|
+
for (let c = 0; c < 3; c++)
|
|
94
|
+
for (let r = 0; r < 3; r++) rotation[c * 4 + r] = m[c * 4 + r] / scales[c];
|
|
95
|
+
const axes = [
|
|
96
|
+
Array.from(rotation.slice(0, 3)),
|
|
97
|
+
Array.from(rotation.slice(4, 7)),
|
|
98
|
+
Array.from(rotation.slice(8, 11)),
|
|
99
|
+
];
|
|
100
|
+
if (
|
|
101
|
+
Math.abs(dot(axes[0], axes[1])) +
|
|
102
|
+
Math.abs(dot(axes[0], axes[2])) +
|
|
103
|
+
Math.abs(dot(axes[1], axes[2])) >
|
|
104
|
+
1e-3
|
|
105
|
+
)
|
|
106
|
+
return null;
|
|
107
|
+
return { q: quaternion(rotation), s: scales, p: Array.from(m.slice(12, 15)) };
|
|
108
|
+
}
|
|
109
|
+
function slerp(a, b, t) {
|
|
110
|
+
let cosine = dot(a, b);
|
|
111
|
+
if (cosine < 0) {
|
|
112
|
+
b = b.map((x) => -x);
|
|
113
|
+
cosine = -cosine;
|
|
114
|
+
}
|
|
115
|
+
if (cosine > 0.9995) return normalized(a.map((x, i) => x + (b[i] - x) * t));
|
|
116
|
+
const angle = Math.acos(Math.min(1, cosine)),
|
|
117
|
+
d = Math.sin(angle);
|
|
118
|
+
return a.map(
|
|
119
|
+
(x, i) => (x * Math.sin((1 - t) * angle) + b[i] * Math.sin(t * angle)) / d,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
function compose({ q: [x, y, z, w], s, p }) {
|
|
123
|
+
const m = identity();
|
|
124
|
+
m[0] = 1 - 2 * (y * y + z * z);
|
|
125
|
+
m[1] = 2 * (x * y + z * w);
|
|
126
|
+
m[2] = 2 * (x * z - y * w);
|
|
127
|
+
m[4] = 2 * (x * y - z * w);
|
|
128
|
+
m[5] = 1 - 2 * (x * x + z * z);
|
|
129
|
+
m[6] = 2 * (y * z + x * w);
|
|
130
|
+
m[8] = 2 * (x * z + y * w);
|
|
131
|
+
m[9] = 2 * (y * z - x * w);
|
|
132
|
+
m[10] = 1 - 2 * (x * x + y * y);
|
|
133
|
+
for (let c = 0; c < 3; c++) for (let r = 0; r < 3; r++) m[c * 4 + r] *= s[c];
|
|
134
|
+
m.set(p, 12);
|
|
135
|
+
return m;
|
|
136
|
+
}
|
|
137
|
+
export function interpolateTransform(a, b, t) {
|
|
138
|
+
if (t <= 0) return new Float64Array(a);
|
|
139
|
+
if (t >= 1) return new Float64Array(b);
|
|
140
|
+
const from = decompose(a),
|
|
141
|
+
to = decompose(b);
|
|
142
|
+
if (!from?.q || !to?.q || from.s.some((x, i) => x * to.s[i] <= 0))
|
|
143
|
+
return null;
|
|
144
|
+
return compose({
|
|
145
|
+
q: slerp(from.q, to.q, t),
|
|
146
|
+
s: from.s.map((x, i) => x + (to.s[i] - x) * t),
|
|
147
|
+
p: from.p.map((x, i) => x + (to.p[i] - x) * t),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function point(m, x, y, z) {
|
|
151
|
+
const w = m[3] * x + m[7] * y + m[11] * z + m[15];
|
|
152
|
+
if (Math.abs(w) < 1e-10) return null;
|
|
153
|
+
return [0, 1, 2].map(
|
|
154
|
+
(r) => (m[r] * x + m[4 + r] * y + m[8 + r] * z + m[12 + r]) / w,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
function camera(frame) {
|
|
158
|
+
const vp = frame.slice(0, 16),
|
|
159
|
+
inv = inverseMatrix(vp);
|
|
160
|
+
if (!inv) return null;
|
|
161
|
+
const center = point(inv, 0, 0, -1),
|
|
162
|
+
right = point(inv, 1, 0, -1),
|
|
163
|
+
up = point(inv, 0, 1, -1),
|
|
164
|
+
far = point(inv, 0, 0, 1);
|
|
165
|
+
if (!center || !right || !up || !far) return null;
|
|
166
|
+
const axes = [
|
|
167
|
+
normalized(right.map((v, i) => v - center[i])),
|
|
168
|
+
normalized(up.map((v, i) => v - center[i])),
|
|
169
|
+
normalized(center.map((v, i) => v - far[i])),
|
|
170
|
+
];
|
|
171
|
+
if (axes.some((a) => !a)) return null;
|
|
172
|
+
const world = identity();
|
|
173
|
+
axes.forEach((a, i) => world.set(a, i * 4));
|
|
174
|
+
const position = frame[51] === 1 ? frame.slice(48, 51) : point(inv, 0, 0, 0);
|
|
175
|
+
if (!position) return null;
|
|
176
|
+
world.set(position, 12);
|
|
177
|
+
return { world, projection: multiplyMatrix(vp, world) };
|
|
178
|
+
}
|
|
179
|
+
export function blendFramePose(from, to, t) {
|
|
180
|
+
const out = new Float32Array(to);
|
|
181
|
+
if (t <= 0) {
|
|
182
|
+
out.set(from.subarray(0, 32), 0);
|
|
183
|
+
out.set(from.subarray(48, 51), 48);
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
if (t >= 1) return out;
|
|
187
|
+
const a = camera(from),
|
|
188
|
+
b = camera(to);
|
|
189
|
+
if (!a || !b || from[51] !== to[51]) return null;
|
|
190
|
+
// Interpolate within one projection family, never perspective to orthographic.
|
|
191
|
+
if (Math.abs(a.projection[15] - b.projection[15]) > 1e-4) return null;
|
|
192
|
+
const world = interpolateTransform(a.world, b.world, t);
|
|
193
|
+
if (!world) return null;
|
|
194
|
+
const view = inverseMatrix(world);
|
|
195
|
+
if (!view) return null;
|
|
196
|
+
const projection = a.projection.map((v, i) => v + (b.projection[i] - v) * t);
|
|
197
|
+
const vp = multiplyMatrix(projection, view),
|
|
198
|
+
inv = inverseMatrix(vp);
|
|
199
|
+
if (!inv) return null;
|
|
200
|
+
out.set(vp, 0);
|
|
201
|
+
out.set(inv, 16);
|
|
202
|
+
if (to[51] === 1) out.set(world.slice(12, 15), 48);
|
|
203
|
+
else for (let i = 48; i < 51; i++) out[i] = from[i] + (to[i] - from[i]) * t;
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
export function blendDrawPose(from, to, t) {
|
|
207
|
+
const out = new Float32Array(to),
|
|
208
|
+
world = interpolateTransform(from.slice(0, 16), to.slice(0, 16), t);
|
|
209
|
+
if (!world) return null;
|
|
210
|
+
const normal = inverseMatrix(world);
|
|
211
|
+
if (!normal) return null;
|
|
212
|
+
out.set(world, 0);
|
|
213
|
+
if (t <= 0) out.set(from.subarray(16, 32), 16);
|
|
214
|
+
// Native normalMatrix is an inverse-transpose of the world linear transform.
|
|
215
|
+
// Retain its unused fourth row/column and every non-pose field verbatim.
|
|
216
|
+
if (t > 0 && t < 1)
|
|
217
|
+
for (let c = 0; c < 3; c++)
|
|
218
|
+
for (let r = 0; r < 3; r++) out[16 + c * 4 + r] = normal[r * 4 + c];
|
|
219
|
+
for (const i of [37, 38, 39, 88, 89, 90, 91])
|
|
220
|
+
out[i] = from[i] + (to[i] - from[i]) * t;
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function createPoseTransition(gl, { assertAvailable = () => {} } = {}) {
|
|
225
|
+
const blocks = new WeakMap(),
|
|
226
|
+
indexed = new Map(),
|
|
227
|
+
buffers = new Map(),
|
|
228
|
+
methods = new Map();
|
|
229
|
+
const ids = new WeakMap();
|
|
230
|
+
let serial = 0;
|
|
231
|
+
const id = (value) => {
|
|
232
|
+
if (!value) return 0;
|
|
233
|
+
if (!ids.has(value)) ids.set(value, ++serial);
|
|
234
|
+
return ids.get(value);
|
|
235
|
+
};
|
|
236
|
+
let bound = null,
|
|
237
|
+
program = null,
|
|
238
|
+
vao = null,
|
|
239
|
+
frame = new Map(),
|
|
240
|
+
previous = new Map(),
|
|
241
|
+
source = null;
|
|
242
|
+
const starts = new Map();
|
|
243
|
+
let now = 0,
|
|
244
|
+
expires = 0,
|
|
245
|
+
duration = 180,
|
|
246
|
+
ready = true;
|
|
247
|
+
const counters = {
|
|
248
|
+
transitions: 0,
|
|
249
|
+
draws: 0,
|
|
250
|
+
cameras: 0,
|
|
251
|
+
joints: 0,
|
|
252
|
+
unmatched: 0,
|
|
253
|
+
unsupported: 0,
|
|
254
|
+
};
|
|
255
|
+
function programBlocks(p) {
|
|
256
|
+
let value = blocks.get(p);
|
|
257
|
+
if (!value) {
|
|
258
|
+
value = new Map();
|
|
259
|
+
blocks.set(p, value);
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
function bytes(data, offset = 0, length) {
|
|
264
|
+
if (!ArrayBuffer.isView(data) || !Number.isInteger(data.BYTES_PER_ELEMENT))
|
|
265
|
+
return null;
|
|
266
|
+
const count = length === undefined ? data.length - offset : length;
|
|
267
|
+
if (!Number.isInteger(count) || count < 0) return null;
|
|
268
|
+
return new Uint8Array(
|
|
269
|
+
data.buffer,
|
|
270
|
+
data.byteOffset + offset * data.BYTES_PER_ELEMENT,
|
|
271
|
+
count * data.BYTES_PER_ELEMENT,
|
|
272
|
+
).slice();
|
|
273
|
+
}
|
|
274
|
+
function upload(range, values) {
|
|
275
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, range.buffer);
|
|
276
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, range.offset, values);
|
|
277
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, bound);
|
|
278
|
+
}
|
|
279
|
+
function rangeFor(name, size) {
|
|
280
|
+
const entry = [...(blocks.get(program)?.values() ?? [])].find(
|
|
281
|
+
(x) => x.name === name && x.binding !== undefined,
|
|
282
|
+
);
|
|
283
|
+
const range = entry && indexed.get(entry.binding),
|
|
284
|
+
data = range && buffers.get(range.buffer);
|
|
285
|
+
if (
|
|
286
|
+
!range ||
|
|
287
|
+
!data ||
|
|
288
|
+
(range.size ?? data.length) !== size ||
|
|
289
|
+
range.offset < 0 ||
|
|
290
|
+
range.offset + size > data.length
|
|
291
|
+
)
|
|
292
|
+
return null;
|
|
293
|
+
return { ...range, data, size };
|
|
294
|
+
}
|
|
295
|
+
function pose(name, range, key, blend) {
|
|
296
|
+
if (!range || frame.has(key)) return;
|
|
297
|
+
let output = new Float32Array(
|
|
298
|
+
range.data.slice(range.offset, range.offset + range.size).buffer,
|
|
299
|
+
);
|
|
300
|
+
const old = source?.get(key);
|
|
301
|
+
if (source) {
|
|
302
|
+
if (old && old.length === output.length) {
|
|
303
|
+
const indices =
|
|
304
|
+
name === 'draws'
|
|
305
|
+
? [
|
|
306
|
+
...Array.from({ length: 16 }, (_, i) => i),
|
|
307
|
+
37,
|
|
308
|
+
38,
|
|
309
|
+
39,
|
|
310
|
+
88,
|
|
311
|
+
89,
|
|
312
|
+
90,
|
|
313
|
+
91,
|
|
314
|
+
]
|
|
315
|
+
: name === 'cameras'
|
|
316
|
+
? [...Array.from({ length: 16 }, (_, i) => i), 48, 49, 50]
|
|
317
|
+
: Array.from({ length: output.length }, (_, i) => i);
|
|
318
|
+
const changed = indices.some(
|
|
319
|
+
(i) => Math.abs(old[i] - output[i]) > 1e-5,
|
|
320
|
+
);
|
|
321
|
+
if (!starts.has(key) && ready && changed) starts.set(key, null);
|
|
322
|
+
const start = starts.get(key);
|
|
323
|
+
const progress =
|
|
324
|
+
typeof start === 'number'
|
|
325
|
+
? Math.min(1, Math.max(0, (now - start) / duration))
|
|
326
|
+
: 0;
|
|
327
|
+
if (progress === 1 || !changed) {
|
|
328
|
+
frame.set(key, output);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const alpha = progress * progress * (3 - 2 * progress);
|
|
332
|
+
const mixed = blend(old, output, alpha);
|
|
333
|
+
if (mixed) {
|
|
334
|
+
output = mixed;
|
|
335
|
+
upload(range, output);
|
|
336
|
+
counters[name]++;
|
|
337
|
+
} else counters.unsupported++;
|
|
338
|
+
} else counters.unmatched++;
|
|
339
|
+
}
|
|
340
|
+
frame.set(key, new Float32Array(output));
|
|
341
|
+
}
|
|
342
|
+
function beforeDraw() {
|
|
343
|
+
if (!program || !vao) return;
|
|
344
|
+
const draw = rangeFor('DrawBlock', 368);
|
|
345
|
+
if (!draw) return;
|
|
346
|
+
// VAOs identify original geometry; range offsets disambiguate instances
|
|
347
|
+
// such as two eyes sharing a mesh. Newly introduced props have no old pose.
|
|
348
|
+
pose('draws', draw, `draw:${id(vao)}:${draw.offset}`, blendDrawPose);
|
|
349
|
+
const cameraRange = rangeFor('FrameBlock', 464);
|
|
350
|
+
pose(
|
|
351
|
+
'cameras',
|
|
352
|
+
cameraRange,
|
|
353
|
+
`frame:${id(cameraRange?.buffer)}`,
|
|
354
|
+
blendFramePose,
|
|
355
|
+
);
|
|
356
|
+
const skin = rangeFor('SkinBlock', 1536);
|
|
357
|
+
if (skin)
|
|
358
|
+
pose('joints', skin, `skin:${id(skin.buffer)}`, (a, b, t) => {
|
|
359
|
+
const out = new Float32Array(b);
|
|
360
|
+
for (let i = 0; i < 384; i += 16) {
|
|
361
|
+
// Unused joint slots can be zero matrices; leave identical slots alone.
|
|
362
|
+
if (a.subarray(i, i + 16).every((v, k) => v === b[i + k])) continue;
|
|
363
|
+
const matrix = interpolateTransform(
|
|
364
|
+
a.slice(i, i + 16),
|
|
365
|
+
b.slice(i, i + 16),
|
|
366
|
+
t,
|
|
367
|
+
);
|
|
368
|
+
if (!matrix) return null;
|
|
369
|
+
out.set(matrix, i);
|
|
370
|
+
}
|
|
371
|
+
return out;
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const api = {
|
|
375
|
+
getUniformBlockIndex(p, name) {
|
|
376
|
+
const index = gl.getUniformBlockIndex(p, name);
|
|
377
|
+
if (index !== gl.INVALID_INDEX)
|
|
378
|
+
programBlocks(p).set(index, { ...programBlocks(p).get(index), name });
|
|
379
|
+
return index;
|
|
380
|
+
},
|
|
381
|
+
uniformBlockBinding(p, index, binding) {
|
|
382
|
+
const entries = programBlocks(p),
|
|
383
|
+
entry = entries.get(index);
|
|
384
|
+
if (entry) entry.binding = binding;
|
|
385
|
+
return gl.uniformBlockBinding(p, index, binding);
|
|
386
|
+
},
|
|
387
|
+
useProgram(p) {
|
|
388
|
+
program = p;
|
|
389
|
+
return gl.useProgram(p);
|
|
390
|
+
},
|
|
391
|
+
bindVertexArray(value) {
|
|
392
|
+
vao = value;
|
|
393
|
+
return gl.bindVertexArray(value);
|
|
394
|
+
},
|
|
395
|
+
bindBuffer(target, value) {
|
|
396
|
+
if (target === gl.UNIFORM_BUFFER) bound = value;
|
|
397
|
+
return gl.bindBuffer(target, value);
|
|
398
|
+
},
|
|
399
|
+
bindBufferRange(target, index, buffer, offset, size) {
|
|
400
|
+
if (target === gl.UNIFORM_BUFFER) {
|
|
401
|
+
bound = buffer;
|
|
402
|
+
indexed.set(index, { buffer, offset, size });
|
|
403
|
+
}
|
|
404
|
+
return gl.bindBufferRange(target, index, buffer, offset, size);
|
|
405
|
+
},
|
|
406
|
+
bindBufferBase(target, index, buffer) {
|
|
407
|
+
if (target === gl.UNIFORM_BUFFER) {
|
|
408
|
+
bound = buffer;
|
|
409
|
+
indexed.set(index, { buffer, offset: 0, size: undefined });
|
|
410
|
+
}
|
|
411
|
+
return gl.bindBufferBase(target, index, buffer);
|
|
412
|
+
},
|
|
413
|
+
bufferData(target, data, usage, offset, length) {
|
|
414
|
+
if (target === gl.UNIFORM_BUFFER && bound) {
|
|
415
|
+
const copy = bytes(data, offset, length);
|
|
416
|
+
if (copy) buffers.set(bound, copy);
|
|
417
|
+
else buffers.delete(bound);
|
|
418
|
+
}
|
|
419
|
+
return gl.bufferData(
|
|
420
|
+
target,
|
|
421
|
+
data,
|
|
422
|
+
usage,
|
|
423
|
+
...(offset === undefined
|
|
424
|
+
? []
|
|
425
|
+
: [offset, ...(length === undefined ? [] : [length])]),
|
|
426
|
+
);
|
|
427
|
+
},
|
|
428
|
+
bufferSubData(target, offset, data, srcOffset, length) {
|
|
429
|
+
if (target === gl.UNIFORM_BUFFER && bound) {
|
|
430
|
+
const copy = bytes(data, srcOffset, length),
|
|
431
|
+
old = buffers.get(bound);
|
|
432
|
+
if (copy && old && offset + copy.length <= old.length)
|
|
433
|
+
old.set(copy, offset);
|
|
434
|
+
else buffers.delete(bound);
|
|
435
|
+
}
|
|
436
|
+
return gl.bufferSubData(
|
|
437
|
+
target,
|
|
438
|
+
offset,
|
|
439
|
+
data,
|
|
440
|
+
...(srcOffset === undefined
|
|
441
|
+
? []
|
|
442
|
+
: [srcOffset, ...(length === undefined ? [] : [length])]),
|
|
443
|
+
);
|
|
444
|
+
},
|
|
445
|
+
deleteBuffer(buffer) {
|
|
446
|
+
buffers.delete(buffer);
|
|
447
|
+
return gl.deleteBuffer(buffer);
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
const context = new Proxy(gl, {
|
|
451
|
+
get(target, key) {
|
|
452
|
+
if (Object.hasOwn(api, key)) {
|
|
453
|
+
if (!methods.has(key))
|
|
454
|
+
methods.set(key, (...args) => {
|
|
455
|
+
assertAvailable();
|
|
456
|
+
return api[key](...args);
|
|
457
|
+
});
|
|
458
|
+
return methods.get(key);
|
|
459
|
+
}
|
|
460
|
+
if (methods.has(key)) return methods.get(key);
|
|
461
|
+
const value = Reflect.get(target, key, target);
|
|
462
|
+
if (typeof value !== 'function') return value;
|
|
463
|
+
const method =
|
|
464
|
+
/^draw(?:Arrays|Elements|RangeElements)(?:Instanced)?$/.test(
|
|
465
|
+
String(key),
|
|
466
|
+
)
|
|
467
|
+
? (...args) => {
|
|
468
|
+
assertAvailable();
|
|
469
|
+
beforeDraw();
|
|
470
|
+
return value(...args);
|
|
471
|
+
}
|
|
472
|
+
: value.bind(target);
|
|
473
|
+
methods.set(key, method);
|
|
474
|
+
return method;
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
return {
|
|
478
|
+
context,
|
|
479
|
+
beginTransition(milliseconds = 180) {
|
|
480
|
+
if (!Number.isFinite(milliseconds) || milliseconds <= 0 || !previous.size)
|
|
481
|
+
return false;
|
|
482
|
+
source = new Map(previous);
|
|
483
|
+
starts.clear();
|
|
484
|
+
ready = false;
|
|
485
|
+
expires = performance.now() + 5000;
|
|
486
|
+
duration = Math.min(milliseconds, 1000);
|
|
487
|
+
counters.transitions++;
|
|
488
|
+
return true;
|
|
489
|
+
},
|
|
490
|
+
beginFrame(time = performance.now()) {
|
|
491
|
+
now = time;
|
|
492
|
+
frame = new Map();
|
|
493
|
+
if (source && now > expires) source = null;
|
|
494
|
+
},
|
|
495
|
+
presented(time = performance.now()) {
|
|
496
|
+
// Compilation and GPU completion can take longer than the blend itself.
|
|
497
|
+
// Start the clock when its source pose is actually displayed, not queued.
|
|
498
|
+
for (const [key, start] of starts)
|
|
499
|
+
if (start === null) starts.set(key, time);
|
|
500
|
+
},
|
|
501
|
+
endFrame(poseReady = true) {
|
|
502
|
+
if (frame.size) previous = frame;
|
|
503
|
+
ready = poseReady;
|
|
504
|
+
},
|
|
505
|
+
cancel() {
|
|
506
|
+
source = null;
|
|
507
|
+
starts.clear();
|
|
508
|
+
},
|
|
509
|
+
stats: () => ({
|
|
510
|
+
...counters,
|
|
511
|
+
active: Boolean(source),
|
|
512
|
+
poses: previous.size,
|
|
513
|
+
}),
|
|
514
|
+
dispose() {
|
|
515
|
+
source = null;
|
|
516
|
+
previous.clear();
|
|
517
|
+
frame.clear();
|
|
518
|
+
buffers.clear();
|
|
519
|
+
indexed.clear();
|
|
520
|
+
methods.clear();
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Exact prepared-scene reuse. These buffers are owned copies, not views into
|
|
2
|
+
// WASM memory or transferable worker replies. Keep the memory cost bounded.
|
|
3
|
+
export function preparationKey(request) {
|
|
4
|
+
if (!request.points && !request.authoredParts) return null;
|
|
5
|
+
return JSON.stringify([
|
|
6
|
+
Array.from(request.appearance),
|
|
7
|
+
request.quality,
|
|
8
|
+
request.key,
|
|
9
|
+
request.activities,
|
|
10
|
+
request.points,
|
|
11
|
+
request.authoredParts,
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createPreparationCache({
|
|
16
|
+
maximumBytes = 8 * 1024 * 1024,
|
|
17
|
+
maximumEntries = 16,
|
|
18
|
+
} = {}) {
|
|
19
|
+
const entries = new Map();
|
|
20
|
+
let bytes = 0,
|
|
21
|
+
hits = 0,
|
|
22
|
+
misses = 0;
|
|
23
|
+
return {
|
|
24
|
+
get(key) {
|
|
25
|
+
if (key === null) return null;
|
|
26
|
+
const entry = entries.get(key);
|
|
27
|
+
if (!entry) {
|
|
28
|
+
misses++;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
entries.delete(key);
|
|
32
|
+
entries.set(key, entry);
|
|
33
|
+
hits++;
|
|
34
|
+
// The receiver may transfer or mutate this result independently.
|
|
35
|
+
return entry.value.slice();
|
|
36
|
+
},
|
|
37
|
+
set(key, value) {
|
|
38
|
+
if (key === null || !(value instanceof Uint8Array) || !value.length)
|
|
39
|
+
return;
|
|
40
|
+
const cost = value.byteLength + key.length * 2;
|
|
41
|
+
if (cost > maximumBytes || maximumEntries < 1) return;
|
|
42
|
+
const previous = entries.get(key);
|
|
43
|
+
if (previous) {
|
|
44
|
+
bytes -= previous.cost;
|
|
45
|
+
entries.delete(key);
|
|
46
|
+
}
|
|
47
|
+
while (
|
|
48
|
+
entries.size &&
|
|
49
|
+
(entries.size >= maximumEntries || bytes + cost > maximumBytes)
|
|
50
|
+
) {
|
|
51
|
+
const [oldKey, old] = entries.entries().next().value;
|
|
52
|
+
entries.delete(oldKey);
|
|
53
|
+
bytes -= old.cost;
|
|
54
|
+
}
|
|
55
|
+
entries.set(key, { value: value.slice(), cost });
|
|
56
|
+
bytes += cost;
|
|
57
|
+
},
|
|
58
|
+
stats: () => ({ entries: entries.size, bytes, maximumBytes, hits, misses }),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// One budget per document, including separately imported/versioned adapters.
|
|
2
|
-
//
|
|
3
|
-
|
|
2
|
+
// Visible moving characters share one atlas/context; admission serializes cold
|
|
3
|
+
// preparation, without suspending neighbours once their first frame is painted.
|
|
4
|
+
// Offscreen/still characters retain their 2D image when their lease is released.
|
|
5
|
+
const key = Symbol.for('bloom.character.shared-render-budget.v1');
|
|
4
6
|
export function createRenderBudget(
|
|
5
7
|
limit = 4,
|
|
6
8
|
schedule = (fn) => requestAnimationFrame(fn),
|
|
@@ -141,4 +143,4 @@ export function createRenderBudget(
|
|
|
141
143
|
}),
|
|
142
144
|
};
|
|
143
145
|
}
|
|
144
|
-
export const renderBudget = (globalThis[key] ??= createRenderBudget());
|
|
146
|
+
export const renderBudget = (globalThis[key] ??= createRenderBudget(Infinity));
|