@oxy.so/bloom 7.1.4 → 7.1.5
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 +19 -114
- package/assets/character-runtime/legacy-engine.mjs +54 -406
- package/assets/character-runtime/legacy-geometry.mjs +85 -242
- package/assets/character-runtime/legacy-worker.mjs +23 -82
- package/assets/character-runtime/managed-avatar.mjs +18 -12
- package/assets/character-runtime/render-budget.mjs +11 -6
- package/assets/character-runtime/runtime.mjs +295 -744
- package/docs/agent-avatar.mdx +8 -45
- package/docs/agent-creator.mdx +3 -13
- 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 +1 -2
- package/lib/commonjs/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/commonjs/agent-avatar/config-character.js +0 -4
- package/lib/commonjs/agent-avatar/config-character.js.map +1 -1
- package/lib/commonjs/agent-avatar/legacy-recipe.js +6 -19
- package/lib/commonjs/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/commonjs/agent-creator/AgentCreatorBase.js +107 -67
- package/lib/commonjs/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/commonjs/agent-creator/CharacterControls.js +5 -23
- package/lib/commonjs/agent-creator/CharacterControls.js.map +1 -1
- package/lib/commonjs/agent-creator/EmotionPicker.js +1 -8
- package/lib/commonjs/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/commonjs/agent-creator/ShapeSilhouette.js +1 -8
- package/lib/commonjs/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/commonjs/agent-creator/constants.js +17 -16
- 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 +1 -2
- package/lib/module/agent-avatar/CharacterAvatar.js.map +1 -1
- package/lib/module/agent-avatar/config-character.js +0 -4
- package/lib/module/agent-avatar/config-character.js.map +1 -1
- package/lib/module/agent-avatar/legacy-recipe.js +6 -19
- package/lib/module/agent-avatar/legacy-recipe.js.map +1 -1
- package/lib/module/agent-creator/AgentCreatorBase.js +109 -69
- package/lib/module/agent-creator/AgentCreatorBase.js.map +1 -1
- package/lib/module/agent-creator/CharacterControls.js +6 -24
- package/lib/module/agent-creator/CharacterControls.js.map +1 -1
- package/lib/module/agent-creator/EmotionPicker.js +1 -8
- package/lib/module/agent-creator/EmotionPicker.js.map +1 -1
- package/lib/module/agent-creator/ShapeSilhouette.js +1 -8
- package/lib/module/agent-creator/ShapeSilhouette.js.map +1 -1
- package/lib/module/agent-creator/constants.js +16 -8
- 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/config-character.d.ts +0 -4
- package/lib/typescript/commonjs/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-avatar/legacy-recipe.d.ts +10 -2
- 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 +1 -2
- package/lib/typescript/commonjs/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/agent-creator/EmotionPicker.d.ts +0 -1
- 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/constants.d.ts +15 -7
- 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/config-character.d.ts +0 -4
- package/lib/typescript/module/agent-avatar/config-character.d.ts.map +1 -1
- package/lib/typescript/module/agent-avatar/legacy-recipe.d.ts +10 -2
- 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 +1 -2
- package/lib/typescript/module/agent-creator/CharacterControls.d.ts.map +1 -1
- package/lib/typescript/module/agent-creator/EmotionPicker.d.ts +0 -1
- 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/constants.d.ts +15 -7
- 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 +1 -4
- package/src/agent-avatar/config-character.ts +6 -39
- package/src/agent-avatar/legacy-recipe.ts +6 -34
- package/src/agent-creator/AgentCreatorBase.tsx +152 -121
- package/src/agent-creator/CharacterControls.tsx +14 -39
- package/src/agent-creator/EmotionPicker.tsx +16 -55
- package/src/agent-creator/ShapeSilhouette.tsx +1 -16
- package/src/agent-creator/constants.ts +14 -47
- package/src/agent-creator/messages.ts +1 -2
- 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/assets/character-runtime/authored-parts.mjs +0 -754
- package/assets/character-runtime/catalog-parts.mjs +0 -362
- package/assets/character-runtime/character-recipe.mjs +0 -247
- package/assets/character-runtime/clippo-geometry.mjs +0 -679
- package/assets/character-runtime/controller-mode.mjs +0 -208
- package/assets/character-runtime/cyclops-geometry.mjs +0 -180
- package/assets/character-runtime/migrated-contours.mjs +0 -2067
- package/assets/character-runtime/migrated-faces.mjs +0 -59
- package/assets/character-runtime/pose-transition.mjs +0 -523
- package/assets/character-runtime/preparation-cache.mjs +0 -60
- package/assets/character-runtime/render-errors.mjs +0 -180
- package/assets/character-runtime/resolution.mjs +0 -10
- package/assets/character-runtime/sha256.mjs +0 -44
- package/assets/character-runtime/shared-programs.mjs +0 -534
- package/assets/character-runtime/shared-surface.mjs +0 -490
- package/assets/character-runtime/shared-textures.mjs +0 -420
- package/assets/character-runtime/thumbnails/eyes/todd.png +0 -0
- package/lib/commonjs/agent-avatar/character-colors.js +0 -19
- package/lib/commonjs/agent-avatar/character-colors.js.map +0 -1
- package/lib/commonjs/agent-avatar/character-shapes.js +0 -106
- package/lib/commonjs/agent-avatar/character-shapes.js.map +0 -1
- package/lib/commonjs/agent-creator/ClippoArtwork.js +0 -63
- package/lib/commonjs/agent-creator/ClippoArtwork.js.map +0 -1
- package/lib/commonjs/agent-creator/CyclopsArtwork.js +0 -45
- package/lib/commonjs/agent-creator/CyclopsArtwork.js.map +0 -1
- package/lib/commonjs/agent-creator/ToddArtwork.js +0 -29
- package/lib/commonjs/agent-creator/ToddArtwork.js.map +0 -1
- package/lib/module/agent-avatar/character-colors.js +0 -15
- package/lib/module/agent-avatar/character-colors.js.map +0 -1
- package/lib/module/agent-avatar/character-shapes.js +0 -97
- package/lib/module/agent-avatar/character-shapes.js.map +0 -1
- package/lib/module/agent-creator/ClippoArtwork.js +0 -59
- package/lib/module/agent-creator/ClippoArtwork.js.map +0 -1
- package/lib/module/agent-creator/CyclopsArtwork.js +0 -41
- package/lib/module/agent-creator/CyclopsArtwork.js.map +0 -1
- package/lib/module/agent-creator/ToddArtwork.js +0 -25
- package/lib/module/agent-creator/ToddArtwork.js.map +0 -1
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts +0 -13
- package/lib/typescript/commonjs/agent-avatar/character-colors.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts +0 -19
- package/lib/typescript/commonjs/agent-avatar/character-shapes.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts +0 -6
- package/lib/typescript/commonjs/agent-creator/ClippoArtwork.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts +0 -5
- package/lib/typescript/commonjs/agent-creator/CyclopsArtwork.d.ts.map +0 -1
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts +0 -6
- package/lib/typescript/commonjs/agent-creator/ToddArtwork.d.ts.map +0 -1
- package/lib/typescript/module/agent-avatar/character-colors.d.ts +0 -13
- package/lib/typescript/module/agent-avatar/character-colors.d.ts.map +0 -1
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts +0 -19
- package/lib/typescript/module/agent-avatar/character-shapes.d.ts.map +0 -1
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts +0 -6
- package/lib/typescript/module/agent-creator/ClippoArtwork.d.ts.map +0 -1
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts +0 -5
- package/lib/typescript/module/agent-creator/CyclopsArtwork.d.ts.map +0 -1
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts +0 -6
- package/lib/typescript/module/agent-creator/ToddArtwork.d.ts.map +0 -1
- package/src/agent-avatar/character-colors.ts +0 -12
- package/src/agent-avatar/character-shapes.ts +0 -122
- package/src/agent-creator/ClippoArtwork.tsx +0 -49
- package/src/agent-creator/CyclopsArtwork.tsx +0 -27
- package/src/agent-creator/ToddArtwork.tsx +0 -24
|
@@ -1,679 +0,0 @@
|
|
|
1
|
-
// Clippo is a native prepared-scene mesh, not a second renderer. The original
|
|
2
|
-
// character controllers, lighting, gaze, blink and activity payload stay in use.
|
|
3
|
-
import {
|
|
4
|
-
authoredAssemblyRecords,
|
|
5
|
-
translateAuthoredRecord,
|
|
6
|
-
transformAuthoredRecord,
|
|
7
|
-
} from './authored-parts.mjs';
|
|
8
|
-
import { decodeAppearance } from './appearance-codec.mjs';
|
|
9
|
-
import { sha256 } from './sha256.mjs';
|
|
10
|
-
const encoder = new TextEncoder();
|
|
11
|
-
const BODY = ':body:no-shadow:studio-fur-v6:';
|
|
12
|
-
const VERSION = 'clippo-tube-v1';
|
|
13
|
-
const fail = (message) => {
|
|
14
|
-
throw new Error(`Clippo geometry: ${message}`);
|
|
15
|
-
};
|
|
16
|
-
const unit = (p) => {
|
|
17
|
-
const n = Math.hypot(...p);
|
|
18
|
-
if (!(n > 1e-9)) fail('degenerate tangent');
|
|
19
|
-
return p.map((x) => x / n);
|
|
20
|
-
};
|
|
21
|
-
const point = ([x, y, z = 0]) => [(x - 650) / 240, (400 - y) / 240, z];
|
|
22
|
-
export const CLIPPO_EYES = Object.freeze([
|
|
23
|
-
Object.freeze({
|
|
24
|
-
center: [-102 / 240, 135 / 240, 0.17],
|
|
25
|
-
radius: [0.255, 0.265, 0.14],
|
|
26
|
-
}),
|
|
27
|
-
Object.freeze({
|
|
28
|
-
center: [46 / 240, 93 / 240, 0.17],
|
|
29
|
-
radius: [0.255, 0.265, 0.14],
|
|
30
|
-
}),
|
|
31
|
-
]);
|
|
32
|
-
/** Eye size follows the solid face, independently from native eye spacing.
|
|
33
|
-
* A narrow native gap is a placement choice, not a request for shallow eyes. */
|
|
34
|
-
export function clippoFaceEyeFit(bodyBounds, anchor) {
|
|
35
|
-
const values = [
|
|
36
|
-
...bodyBounds.min,
|
|
37
|
-
...bodyBounds.max,
|
|
38
|
-
...anchor.min,
|
|
39
|
-
...anchor.max,
|
|
40
|
-
];
|
|
41
|
-
if (
|
|
42
|
-
!values.every(Number.isFinite) ||
|
|
43
|
-
[bodyBounds, anchor].some((bounds) =>
|
|
44
|
-
bounds.min.some((v, k) => v > bounds.max[k]),
|
|
45
|
-
)
|
|
46
|
-
)
|
|
47
|
-
fail('invalid eye fitting bounds');
|
|
48
|
-
const eyeScale = Math.max(
|
|
49
|
-
0.65,
|
|
50
|
-
Math.min(1.2, (bodyBounds.max[0] - bodyBounds.min[0]) / 2),
|
|
51
|
-
);
|
|
52
|
-
return {
|
|
53
|
-
eyeScale,
|
|
54
|
-
center: [
|
|
55
|
-
(anchor.min[0] + anchor.max[0]) / 2,
|
|
56
|
-
(anchor.min[1] + anchor.max[1]) / 2,
|
|
57
|
-
anchor.min[2] + 0.14 * eyeScale,
|
|
58
|
-
],
|
|
59
|
-
radius: [0.2, 0.21, 0.14].map((v) => v * eyeScale),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
// Continuous open wire, from the inner endpoint through the upper curl to the
|
|
63
|
-
// outer endpoint. The two holes come from geometry, never an alpha cutout.
|
|
64
|
-
const PATH = [
|
|
65
|
-
[
|
|
66
|
-
[604, 383],
|
|
67
|
-
[590, 484],
|
|
68
|
-
[611, 528],
|
|
69
|
-
[656, 530],
|
|
70
|
-
],
|
|
71
|
-
[
|
|
72
|
-
[656, 530],
|
|
73
|
-
[707, 532],
|
|
74
|
-
[688, 471],
|
|
75
|
-
[684, 417],
|
|
76
|
-
],
|
|
77
|
-
[
|
|
78
|
-
[684, 417],
|
|
79
|
-
[675, 350],
|
|
80
|
-
[692, 264],
|
|
81
|
-
[711, 218],
|
|
82
|
-
],
|
|
83
|
-
[
|
|
84
|
-
[711, 218],
|
|
85
|
-
[742, 142],
|
|
86
|
-
[689, 109],
|
|
87
|
-
[648, 119],
|
|
88
|
-
],
|
|
89
|
-
[
|
|
90
|
-
[648, 119],
|
|
91
|
-
[596, 115],
|
|
92
|
-
[570, 179],
|
|
93
|
-
[553, 235],
|
|
94
|
-
],
|
|
95
|
-
[
|
|
96
|
-
[553, 235],
|
|
97
|
-
[521, 323],
|
|
98
|
-
[520, 395],
|
|
99
|
-
[536, 482],
|
|
100
|
-
],
|
|
101
|
-
[
|
|
102
|
-
[536, 482],
|
|
103
|
-
[553, 589],
|
|
104
|
-
[591, 669],
|
|
105
|
-
[663, 657],
|
|
106
|
-
],
|
|
107
|
-
[
|
|
108
|
-
[663, 657],
|
|
109
|
-
[744, 650],
|
|
110
|
-
[769, 616],
|
|
111
|
-
[754, 526],
|
|
112
|
-
],
|
|
113
|
-
[
|
|
114
|
-
[754, 526],
|
|
115
|
-
[737, 442],
|
|
116
|
-
[743, 403],
|
|
117
|
-
[778, 360],
|
|
118
|
-
],
|
|
119
|
-
];
|
|
120
|
-
function bezier(points, t) {
|
|
121
|
-
const s = 1 - t;
|
|
122
|
-
return [0, 1, 2].map(
|
|
123
|
-
(k) =>
|
|
124
|
-
s * s * s * points[0][k] +
|
|
125
|
-
3 * s * s * t * points[1][k] +
|
|
126
|
-
3 * s * t * t * points[2][k] +
|
|
127
|
-
t * t * t * points[3][k],
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
export function clippoPath(steps = 12) {
|
|
131
|
-
if (!Number.isInteger(steps) || steps < 4 || steps > 64)
|
|
132
|
-
fail('path resolution');
|
|
133
|
-
return PATH.flatMap((curve, index) =>
|
|
134
|
-
Array.from(
|
|
135
|
-
{ length: steps + (index === PATH.length - 1 ? 1 : 0) },
|
|
136
|
-
(_, i) => bezier(curve.map(point), i / steps),
|
|
137
|
-
),
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
/** A rounded, closed tube around an open planar centerline. Native vertex layout
|
|
141
|
-
* stores three position/normal poses, two constant weights and a bend weight.
|
|
142
|
-
* The editable original circle has zero position deltas; preserve that contract.
|
|
143
|
-
*/
|
|
144
|
-
export function tubeMesh(path, radius = 0.063, sides = 12) {
|
|
145
|
-
if (
|
|
146
|
-
!Array.isArray(path) ||
|
|
147
|
-
path.length < 2 ||
|
|
148
|
-
path.length > 10000 ||
|
|
149
|
-
!Number.isFinite(radius) ||
|
|
150
|
-
radius <= 0 ||
|
|
151
|
-
radius > 1 ||
|
|
152
|
-
!Number.isInteger(sides) ||
|
|
153
|
-
sides < 6 ||
|
|
154
|
-
sides > 64
|
|
155
|
-
)
|
|
156
|
-
fail('tube parameters');
|
|
157
|
-
if (
|
|
158
|
-
path.some(
|
|
159
|
-
(p) =>
|
|
160
|
-
!Array.isArray(p) ||
|
|
161
|
-
p.length !== 3 ||
|
|
162
|
-
p.some((v) => !Number.isFinite(v)),
|
|
163
|
-
)
|
|
164
|
-
)
|
|
165
|
-
fail('non-finite path');
|
|
166
|
-
const ringCount = path.length + 8,
|
|
167
|
-
vertices = new Float32Array(ringCount * sides * 24),
|
|
168
|
-
indices = [];
|
|
169
|
-
const tangent = path.map((p, i) =>
|
|
170
|
-
unit(
|
|
171
|
-
path[Math.min(i + 1, path.length - 1)].map(
|
|
172
|
-
(v, k) => v - path[Math.max(0, i - 1)][k],
|
|
173
|
-
),
|
|
174
|
-
),
|
|
175
|
-
);
|
|
176
|
-
const rings = [];
|
|
177
|
-
for (let i = 4; i > 0; i--) {
|
|
178
|
-
const a = (i * Math.PI) / 8;
|
|
179
|
-
rings.push({
|
|
180
|
-
p: path[0].map((v, k) => v - tangent[0][k] * radius * Math.sin(a)),
|
|
181
|
-
t: tangent[0],
|
|
182
|
-
r: radius * Math.cos(a),
|
|
183
|
-
cap: -Math.sin(a),
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
path.forEach((p, i) => rings.push({ p, t: tangent[i], r: radius, cap: 0 }));
|
|
187
|
-
for (let i = 1; i <= 4; i++) {
|
|
188
|
-
const a = (i * Math.PI) / 8;
|
|
189
|
-
rings.push({
|
|
190
|
-
p: path
|
|
191
|
-
.at(-1)
|
|
192
|
-
.map((v, k) => v + tangent.at(-1)[k] * radius * Math.sin(a)),
|
|
193
|
-
t: tangent.at(-1),
|
|
194
|
-
r: radius * Math.cos(a),
|
|
195
|
-
cap: Math.sin(a),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
rings.forEach(({ p, t, r, cap }, i) => {
|
|
199
|
-
const n = unit([-t[1], t[0], 0]);
|
|
200
|
-
for (let j = 0; j < sides; j++) {
|
|
201
|
-
const a = (j / sides) * Math.PI * 2,
|
|
202
|
-
radial = n.map((v, k) => v * Math.cos(a) + (k === 2 ? Math.sin(a) : 0));
|
|
203
|
-
const normal = unit(radial.map((v, k) => (v * r) / radius + t[k] * cap));
|
|
204
|
-
const at = (i * sides + j) * 24;
|
|
205
|
-
vertices.set(
|
|
206
|
-
p.map((v, k) => v + radial[k] * r),
|
|
207
|
-
at,
|
|
208
|
-
);
|
|
209
|
-
for (const o of [3, 9, 15, 21]) vertices.set(normal, at + o);
|
|
210
|
-
vertices[at + 18] = vertices[at + 19] = 1;
|
|
211
|
-
vertices[at + 20] = 0;
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
for (let i = 0; i < rings.length - 1; i++)
|
|
215
|
-
for (let j = 0; j < sides; j++) {
|
|
216
|
-
const a = i * sides + j,
|
|
217
|
-
b = i * sides + ((j + 1) % sides),
|
|
218
|
-
c = (i + 1) * sides + j,
|
|
219
|
-
d = (i + 1) * sides + ((j + 1) % sides);
|
|
220
|
-
indices.push(a, b, c, b, d, c);
|
|
221
|
-
}
|
|
222
|
-
return { vertices, indices: Uint32Array.from(indices) };
|
|
223
|
-
}
|
|
224
|
-
function boundsOf(vertices) {
|
|
225
|
-
const min = [Infinity, Infinity, Infinity],
|
|
226
|
-
max = [-Infinity, -Infinity, -Infinity];
|
|
227
|
-
for (let i = 0; i < vertices.length; i += 24)
|
|
228
|
-
for (let k = 0; k < 3; k++) {
|
|
229
|
-
const v = vertices[i + k];
|
|
230
|
-
if (!Number.isFinite(v)) fail('non-finite vertex');
|
|
231
|
-
min[k] = Math.min(min[k], v);
|
|
232
|
-
max[k] = Math.max(max[k], v);
|
|
233
|
-
}
|
|
234
|
-
return { min, max };
|
|
235
|
-
}
|
|
236
|
-
function boundsUnion(records) {
|
|
237
|
-
return {
|
|
238
|
-
min: [0, 1, 2].map((k) => Math.min(...records.map((r) => r.bounds.min[k]))),
|
|
239
|
-
max: [0, 1, 2].map((k) => Math.max(...records.map((r) => r.bounds.max[k]))),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function floats(prefix, offset, values) {
|
|
243
|
-
const view = new DataView(
|
|
244
|
-
prefix.buffer,
|
|
245
|
-
prefix.byteOffset,
|
|
246
|
-
prefix.byteLength,
|
|
247
|
-
);
|
|
248
|
-
values.forEach((v, k) => view.setFloat32(offset + k * 4, v, true));
|
|
249
|
-
}
|
|
250
|
-
function meshRecord(
|
|
251
|
-
bytes,
|
|
252
|
-
source,
|
|
253
|
-
mesh,
|
|
254
|
-
name,
|
|
255
|
-
{ body = false, prefix: customPrefix } = {},
|
|
256
|
-
) {
|
|
257
|
-
const prefix =
|
|
258
|
-
customPrefix ?? bytes.slice(source.start, source.nameOffset - 4),
|
|
259
|
-
bounds = boundsOf(mesh.vertices);
|
|
260
|
-
floats(prefix, 0, bounds.min);
|
|
261
|
-
floats(prefix, 12, bounds.max);
|
|
262
|
-
const encoded = encoder.encode(name),
|
|
263
|
-
furBytes = body ? 4 : 0;
|
|
264
|
-
if (mesh.vertices.length / 24 > 65535) fail('16-bit mesh limit');
|
|
265
|
-
const packedIndices = Uint16Array.from(mesh.indices);
|
|
266
|
-
const out = new Uint8Array(
|
|
267
|
-
prefix.length +
|
|
268
|
-
4 +
|
|
269
|
-
encoded.length +
|
|
270
|
-
4 +
|
|
271
|
-
mesh.vertices.byteLength +
|
|
272
|
-
4 +
|
|
273
|
-
packedIndices.byteLength +
|
|
274
|
-
4 +
|
|
275
|
-
7 +
|
|
276
|
-
furBytes +
|
|
277
|
-
27,
|
|
278
|
-
),
|
|
279
|
-
view = new DataView(out.buffer);
|
|
280
|
-
let at = 0;
|
|
281
|
-
out.set(prefix, at);
|
|
282
|
-
at += prefix.length;
|
|
283
|
-
view.setUint32(at, encoded.length, true);
|
|
284
|
-
at += 4;
|
|
285
|
-
out.set(encoded, at);
|
|
286
|
-
at += encoded.length;
|
|
287
|
-
view.setUint32(at, mesh.vertices.length / 24, true);
|
|
288
|
-
at += 4;
|
|
289
|
-
out.set(
|
|
290
|
-
new Uint8Array(
|
|
291
|
-
mesh.vertices.buffer,
|
|
292
|
-
mesh.vertices.byteOffset,
|
|
293
|
-
mesh.vertices.byteLength,
|
|
294
|
-
),
|
|
295
|
-
at,
|
|
296
|
-
);
|
|
297
|
-
at += mesh.vertices.byteLength;
|
|
298
|
-
at += 4;
|
|
299
|
-
view.setUint32(at, packedIndices.length, true);
|
|
300
|
-
at += 4;
|
|
301
|
-
out.set(new Uint8Array(packedIndices.buffer), at);
|
|
302
|
-
at += packedIndices.byteLength; // native 16-bit index stream
|
|
303
|
-
let sourceFlags = source.vertexOffset + source.vertexCount * 96;
|
|
304
|
-
const sourceView = new DataView(
|
|
305
|
-
bytes.buffer,
|
|
306
|
-
bytes.byteOffset,
|
|
307
|
-
bytes.byteLength,
|
|
308
|
-
);
|
|
309
|
-
sourceFlags += 4 + sourceView.getUint32(sourceFlags, true) * 4;
|
|
310
|
-
sourceFlags += 4 + sourceView.getUint32(sourceFlags, true) * 2;
|
|
311
|
-
sourceFlags += 4;
|
|
312
|
-
at += 4;
|
|
313
|
-
out[at++] = bytes[sourceFlags];
|
|
314
|
-
out[at++] = bytes[sourceFlags + 1];
|
|
315
|
-
out[at++] = body ? 1 : 0;
|
|
316
|
-
if (body) at += 4; // fur layout, zero fur strands
|
|
317
|
-
const trailer = bytes.slice(source.geometryEnd, source.geometryEnd + 27);
|
|
318
|
-
out.set(trailer, at);
|
|
319
|
-
view.setUint32(at, mesh.vertices.length / 24, true);
|
|
320
|
-
view.setUint32(at + 4, mesh.indices.length, true);
|
|
321
|
-
return { bytes: out, name, bounds };
|
|
322
|
-
}
|
|
323
|
-
function nativeMesh(bytes, part) {
|
|
324
|
-
const vertices = new Float32Array(part.vertexCount * 24),
|
|
325
|
-
view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
326
|
-
for (let i = 0; i < vertices.length; i++)
|
|
327
|
-
vertices[i] = view.getFloat32(part.vertexOffset + i * 4, true);
|
|
328
|
-
let at = part.vertexOffset + part.vertexCount * 96;
|
|
329
|
-
const n32 = view.getUint32(at, true);
|
|
330
|
-
at += 4;
|
|
331
|
-
const indices = [];
|
|
332
|
-
for (let i = 0; i < n32; i++) indices.push(view.getUint32(at + i * 4, true));
|
|
333
|
-
at += n32 * 4;
|
|
334
|
-
const n16 = view.getUint32(at, true);
|
|
335
|
-
at += 4;
|
|
336
|
-
for (let i = 0; i < n16; i++) indices.push(view.getUint16(at + i * 2, true));
|
|
337
|
-
return { vertices, indices: Uint32Array.from(indices) };
|
|
338
|
-
}
|
|
339
|
-
function transformMesh(mesh, origin, scale, center) {
|
|
340
|
-
for (let i = 0; i < mesh.vertices.length; i += 24) {
|
|
341
|
-
for (let k = 0; k < 3; k++)
|
|
342
|
-
mesh.vertices[i + k] =
|
|
343
|
-
(mesh.vertices[i + k] - origin[k]) * scale[k] + center[k];
|
|
344
|
-
for (const offset of [6, 12])
|
|
345
|
-
for (let k = 0; k < 3; k++) mesh.vertices[i + offset + k] *= scale[k];
|
|
346
|
-
for (const offset of [3, 9, 15, 21])
|
|
347
|
-
mesh.vertices.set(
|
|
348
|
-
unit([0, 1, 2].map((k) => mesh.vertices[i + offset + k] / scale[k])),
|
|
349
|
-
i + offset,
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
return mesh;
|
|
353
|
-
}
|
|
354
|
-
function centerOf(b) {
|
|
355
|
-
return b.min.map((v, k) => (v + b.max[k]) / 2);
|
|
356
|
-
}
|
|
357
|
-
async function identity(value) {
|
|
358
|
-
return sha256(encoder.encode(JSON.stringify([VERSION, value])));
|
|
359
|
-
}
|
|
360
|
-
function record(bytes, part) {
|
|
361
|
-
return {
|
|
362
|
-
bytes: bytes.slice(part.start, part.end),
|
|
363
|
-
name: part.name,
|
|
364
|
-
bounds: part.bounds,
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
function eyeParts(bytes, parts, selectedEye) {
|
|
368
|
-
const decoder = new TextDecoder();
|
|
369
|
-
return parts.filter((p) => {
|
|
370
|
-
const prefix = decoder.decode(
|
|
371
|
-
bytes.subarray(p.start + 24, p.nameOffset - 4),
|
|
372
|
-
);
|
|
373
|
-
const raw = bytes.subarray(p.start + 24, p.nameOffset - 4);
|
|
374
|
-
const label = encoder.encode(selectedEye);
|
|
375
|
-
for (let i = 0; i + 4 + label.length <= raw.length; i++)
|
|
376
|
-
if (
|
|
377
|
-
new DataView(raw.buffer, raw.byteOffset + i, 4).getUint32(0, true) ===
|
|
378
|
-
label.length &&
|
|
379
|
-
label.every((v, k) => raw[i + 4 + k] === v)
|
|
380
|
-
)
|
|
381
|
-
return true;
|
|
382
|
-
return prefix.includes('source_integrated_eye');
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
function eyePrefix(bytes, part, origin, scale, center) {
|
|
386
|
-
const prefix = bytes.slice(part.start, part.nameOffset - 4),
|
|
387
|
-
v = new DataView(prefix.buffer);
|
|
388
|
-
floats(
|
|
389
|
-
prefix,
|
|
390
|
-
40,
|
|
391
|
-
[0, 1, 2].map(
|
|
392
|
-
(k) =>
|
|
393
|
-
(v.getFloat32(40 + k * 4, true) - origin[k]) * scale[k] + center[k],
|
|
394
|
-
),
|
|
395
|
-
); // The original eye effect stores its local half-width/half-height after the
|
|
396
|
-
// material name and normal basis. Scaling these with the mesh preserves blink.
|
|
397
|
-
if (v.getUint32(108, true) !== 0) fail('unexpected eye attachment metadata');
|
|
398
|
-
const effectAt = 116 + v.getUint32(112, true) + 36;
|
|
399
|
-
if (prefix[effectAt] !== 5) fail('unknown native eye effect');
|
|
400
|
-
for (let k = 0; k < 2; k++) {
|
|
401
|
-
const at = effectAt + 2 + k * 4;
|
|
402
|
-
v.setFloat32(at, v.getFloat32(at, true) * scale[k], true);
|
|
403
|
-
}
|
|
404
|
-
return prefix;
|
|
405
|
-
}
|
|
406
|
-
/** Replace selected Clippo geometry while preserving the original scene footer.
|
|
407
|
-
* request.authoredParts.shape / .eyes are independent; other authored pieces
|
|
408
|
-
* can be composed afterwards using these valid native eye anchor records.
|
|
409
|
-
*/
|
|
410
|
-
export async function composeClippoAssembly(module, bytes, request) {
|
|
411
|
-
const selected = request.authoredParts ?? {},
|
|
412
|
-
shape = selected.shape === 'clippo',
|
|
413
|
-
eyes = selected.eyes === 'clippo';
|
|
414
|
-
if (!shape && !eyes) return bytes;
|
|
415
|
-
const info = authoredAssemblyRecords(bytes),
|
|
416
|
-
records = info.records.map((p) => record(bytes, p));
|
|
417
|
-
const smoothSource = module.orbitPrepareAssembly(
|
|
418
|
-
module.presetAppearance('blue_beret'),
|
|
419
|
-
request.quality,
|
|
420
|
-
`${request.key}:clippo-smooth`,
|
|
421
|
-
false,
|
|
422
|
-
);
|
|
423
|
-
if (!smoothSource.bytes || smoothSource.error)
|
|
424
|
-
fail(smoothSource.error || 'smooth material unavailable');
|
|
425
|
-
const smoothBytes = smoothSource.bytes.slice(),
|
|
426
|
-
smoothPart = authoredAssemblyRecords(smoothBytes).records[3];
|
|
427
|
-
const smoothPrefix = (label, color) => {
|
|
428
|
-
const prefix = smoothBytes.slice(
|
|
429
|
-
smoothPart.start,
|
|
430
|
-
smoothPart.nameOffset - 4,
|
|
431
|
-
);
|
|
432
|
-
const name = encoder.encode('beret');
|
|
433
|
-
for (let i = 4; i + 5 <= prefix.length; i++)
|
|
434
|
-
if (
|
|
435
|
-
new DataView(prefix.buffer).getUint32(i - 4, true) === 5 &&
|
|
436
|
-
name.every((v, k) => prefix[i + k] === v)
|
|
437
|
-
)
|
|
438
|
-
prefix.set(encoder.encode(label), i);
|
|
439
|
-
floats(prefix, 40, [0, 0, 0]);
|
|
440
|
-
floats(prefix, 96, [0, 0, 0]);
|
|
441
|
-
if (color) {
|
|
442
|
-
floats(prefix, 28, color);
|
|
443
|
-
floats(prefix, 60, color);
|
|
444
|
-
}
|
|
445
|
-
return prefix;
|
|
446
|
-
};
|
|
447
|
-
if (shape) {
|
|
448
|
-
const mesh = tubeMesh(
|
|
449
|
-
clippoPath(request.quality === 0 ? 8 : 14),
|
|
450
|
-
0.063,
|
|
451
|
-
request.quality === 0 ? 10 : 16,
|
|
452
|
-
);
|
|
453
|
-
const name =
|
|
454
|
-
(await identity(['body', info.body.name, request.quality])) + BODY;
|
|
455
|
-
records[0] = meshRecord(bytes, info.records[0], mesh, name, {
|
|
456
|
-
body: true,
|
|
457
|
-
prefix: smoothPrefix(
|
|
458
|
-
'metal',
|
|
459
|
-
[0, 1, 2].map((k) =>
|
|
460
|
-
new DataView(
|
|
461
|
-
bytes.buffer,
|
|
462
|
-
bytes.byteOffset,
|
|
463
|
-
bytes.byteLength,
|
|
464
|
-
).getFloat32(info.records[0].start + 28 + k * 4, true),
|
|
465
|
-
),
|
|
466
|
-
),
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
const appearance = decodeAppearance(request.appearance);
|
|
470
|
-
const originalEyes = eyeParts(bytes, info.records.slice(1), appearance.eyes);
|
|
471
|
-
if (originalEyes.length < 2) fail('missing native eye anchors');
|
|
472
|
-
const anchors = [-1, 1].map((sign) => {
|
|
473
|
-
const parts = originalEyes.filter(
|
|
474
|
-
(p) => Math.sign(centerOf(p.bounds)[0]) === sign,
|
|
475
|
-
);
|
|
476
|
-
if (!parts.length) fail('missing eye side');
|
|
477
|
-
return { parts, bounds: boundsUnion(parts) };
|
|
478
|
-
});
|
|
479
|
-
// The Clippo body places the two eyes at the reference's sloping upper face.
|
|
480
|
-
if (shape && !eyes)
|
|
481
|
-
for (let side = 0; side < 2; side++)
|
|
482
|
-
for (const p of anchors[side].parts) {
|
|
483
|
-
const origin = centerOf(anchors[side].bounds),
|
|
484
|
-
center = CLIPPO_EYES[side].center;
|
|
485
|
-
records[p.part] = await translateAuthoredRecord(
|
|
486
|
-
bytes,
|
|
487
|
-
p,
|
|
488
|
-
center.map((v, k) => v - origin[k]),
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
if (shape) {
|
|
492
|
-
const center = centerOf(records[0].bounds),
|
|
493
|
-
oldCenter = centerOf(info.body.bounds);
|
|
494
|
-
const faceCenter = [0, 1, 2].map(
|
|
495
|
-
(k) =>
|
|
496
|
-
(centerOf(anchors[0].bounds)[k] + centerOf(anchors[1].bounds)[k]) / 2,
|
|
497
|
-
),
|
|
498
|
-
newFace = [0, 1, 2].map(
|
|
499
|
-
(k) => (CLIPPO_EYES[0].center[k] + CLIPPO_EYES[1].center[k]) / 2,
|
|
500
|
-
);
|
|
501
|
-
for (const p of info.records.slice(1)) {
|
|
502
|
-
if (originalEyes.includes(p)) continue;
|
|
503
|
-
const prefix = bytes.subarray(p.start, p.nameOffset - 4);
|
|
504
|
-
const named = (label) => {
|
|
505
|
-
const needle = encoder.encode(label);
|
|
506
|
-
for (let i = 28; i + 4 + needle.length <= prefix.length; i++)
|
|
507
|
-
if (
|
|
508
|
-
new DataView(prefix.buffer, prefix.byteOffset + i, 4).getUint32(
|
|
509
|
-
0,
|
|
510
|
-
true,
|
|
511
|
-
) === needle.length &&
|
|
512
|
-
needle.every((v, k) => prefix[i + 4 + k] === v)
|
|
513
|
-
)
|
|
514
|
-
return true;
|
|
515
|
-
return false;
|
|
516
|
-
};
|
|
517
|
-
if (appearance.accessories.some(named)) {
|
|
518
|
-
// Scale the whole attachment about the original crown so a narrow metal
|
|
519
|
-
// head does not inherit the much wider circle's hat/headphone footprint.
|
|
520
|
-
const scale = 0.56;
|
|
521
|
-
const origin = [oldCenter[0], info.body.bounds.max[1], oldCenter[2]];
|
|
522
|
-
records[p.part] = await transformAuthoredRecord(bytes, p, {
|
|
523
|
-
scale,
|
|
524
|
-
origin,
|
|
525
|
-
translate: [
|
|
526
|
-
0.02 - origin[0],
|
|
527
|
-
records[0].bounds.max[1] - origin[1],
|
|
528
|
-
-origin[2],
|
|
529
|
-
],
|
|
530
|
-
});
|
|
531
|
-
} else if (appearance.eyewear !== 'none' && named(appearance.eyewear))
|
|
532
|
-
records[p.part] = await translateAuthoredRecord(bytes, p, [
|
|
533
|
-
newFace[0] - faceCenter[0],
|
|
534
|
-
newFace[1] - faceCenter[1],
|
|
535
|
-
0.31 - Math.max(...anchors.map((a) => a.bounds.max[2])),
|
|
536
|
-
]);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (eyes) {
|
|
540
|
-
const prepared = module.orbitPrepareAssembly(
|
|
541
|
-
module.presetAppearance('lime_frog'),
|
|
542
|
-
request.quality,
|
|
543
|
-
`${request.key}:clippo-eyes`,
|
|
544
|
-
false,
|
|
545
|
-
);
|
|
546
|
-
if (!prepared.bytes || prepared.error)
|
|
547
|
-
fail(prepared.error || 'native eye source unavailable');
|
|
548
|
-
const sourceBytes = prepared.bytes.slice(),
|
|
549
|
-
source = authoredAssemblyRecords(sourceBytes);
|
|
550
|
-
const sources = source.records.slice(1, 5);
|
|
551
|
-
if (sources.length !== 4) fail('native eye source changed');
|
|
552
|
-
const replacements = [];
|
|
553
|
-
for (let side = 0; side < 2; side++) {
|
|
554
|
-
const pair = sources.slice(side * 2, side * 2 + 2),
|
|
555
|
-
outer = pair[0],
|
|
556
|
-
origin = centerOf(outer.bounds),
|
|
557
|
-
anchor = anchors[side].bounds;
|
|
558
|
-
const fittedEye = clippoFaceEyeFit(info.body.bounds, anchor);
|
|
559
|
-
const layout = request.faceLayout;
|
|
560
|
-
const eyeScale = layout ? layout.radius / 0.21 : fittedEye.eyeScale;
|
|
561
|
-
const center = shape
|
|
562
|
-
? CLIPPO_EYES[side].center
|
|
563
|
-
: layout
|
|
564
|
-
? [
|
|
565
|
-
layout.centers[side][0],
|
|
566
|
-
layout.centers[side][1],
|
|
567
|
-
layout.centers[side][2] + 0.14 * eyeScale,
|
|
568
|
-
]
|
|
569
|
-
: fittedEye.center;
|
|
570
|
-
const radius = shape
|
|
571
|
-
? CLIPPO_EYES[side].radius
|
|
572
|
-
: layout
|
|
573
|
-
? [0.2, 0.21, 0.14].map((v) => v * eyeScale)
|
|
574
|
-
: fittedEye.radius,
|
|
575
|
-
scale = radius.map(
|
|
576
|
-
(r, k) => (r * 2) / (outer.bounds.max[k] - outer.bounds.min[k]),
|
|
577
|
-
);
|
|
578
|
-
for (const p of pair) {
|
|
579
|
-
const mesh = transformMesh(
|
|
580
|
-
nativeMesh(sourceBytes, p),
|
|
581
|
-
origin,
|
|
582
|
-
scale,
|
|
583
|
-
center,
|
|
584
|
-
),
|
|
585
|
-
prefix = eyePrefix(sourceBytes, p, origin, scale, center);
|
|
586
|
-
if (p === pair[1]) {
|
|
587
|
-
prefix[88] = 0;
|
|
588
|
-
floats(prefix, 28, [0.16, 0.09, 0.09]);
|
|
589
|
-
floats(prefix, 60, [0.16, 0.09, 0.09]);
|
|
590
|
-
}
|
|
591
|
-
const name =
|
|
592
|
-
(await identity([p.name, center, scale, 'clippo-eye'])) +
|
|
593
|
-
p.name.slice(64);
|
|
594
|
-
replacements.push(meshRecord(sourceBytes, p, mesh, name, { prefix }));
|
|
595
|
-
}
|
|
596
|
-
const browPath = shape
|
|
597
|
-
? (side === 0
|
|
598
|
-
? [
|
|
599
|
-
[548, 160],
|
|
600
|
-
[568, 151],
|
|
601
|
-
[592, 153],
|
|
602
|
-
[608, 160],
|
|
603
|
-
]
|
|
604
|
-
: [
|
|
605
|
-
[696, 201],
|
|
606
|
-
[714, 199],
|
|
607
|
-
[734, 212],
|
|
608
|
-
[746, 222],
|
|
609
|
-
]
|
|
610
|
-
).map(point)
|
|
611
|
-
: [
|
|
612
|
-
[-0.14, 0.35, 0],
|
|
613
|
-
[-0.08, 0.41, 0],
|
|
614
|
-
[0.08, 0.4, 0],
|
|
615
|
-
[0.14, 0.35, 0],
|
|
616
|
-
].map((p) => p.map((v, k) => v * eyeScale + center[k]));
|
|
617
|
-
if (shape) browPath.forEach((p) => (p[2] = 0.19));
|
|
618
|
-
const browMesh = tubeMesh(
|
|
619
|
-
Array.from({ length: 17 }, (_, i) => bezier(browPath, i / 16)),
|
|
620
|
-
shape ? 0.065 : 0.045 * eyeScale,
|
|
621
|
-
12,
|
|
622
|
-
),
|
|
623
|
-
prefix = smoothPrefix('brows', [0.16, 0.09, 0.09]);
|
|
624
|
-
const name =
|
|
625
|
-
(await identity(['brow', info.body.name, side, browPath])) +
|
|
626
|
-
':surface:shadow';
|
|
627
|
-
replacements.push(
|
|
628
|
-
meshRecord(bytes, info.records[0], browMesh, name, { prefix }),
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
const eyeIndices = new Set(originalEyes.map((p) => p.part));
|
|
632
|
-
const retained = records.filter((r, i) => !eyeIndices.has(i));
|
|
633
|
-
retained.splice(1, 0, ...replacements);
|
|
634
|
-
records.splice(0, records.length, ...retained);
|
|
635
|
-
}
|
|
636
|
-
const names = records.slice(1).map((r) => r.name),
|
|
637
|
-
bodyName =
|
|
638
|
-
(await identity([records[0].name, names])) +
|
|
639
|
-
BODY +
|
|
640
|
-
names.map((n) => `${n.length}:${n}`).join(':');
|
|
641
|
-
// Body names index the full attachment set in the native cache.
|
|
642
|
-
const first = records[0].bytes,
|
|
643
|
-
oldName = encoder.encode(records[0].name),
|
|
644
|
-
nameAt = findName(first, oldName),
|
|
645
|
-
newName = encoder.encode(bodyName),
|
|
646
|
-
renamed = new Uint8Array(first.length + newName.length - oldName.length);
|
|
647
|
-
renamed.set(first.subarray(0, nameAt - 4));
|
|
648
|
-
new DataView(renamed.buffer).setUint32(nameAt - 4, newName.length, true);
|
|
649
|
-
renamed.set(newName, nameAt);
|
|
650
|
-
renamed.set(first.subarray(nameAt + oldName.length), nameAt + newName.length);
|
|
651
|
-
records[0].bytes = renamed;
|
|
652
|
-
const result = new Uint8Array(
|
|
653
|
-
4 + records.reduce((n, r) => n + r.bytes.length, 0) + info.footer.length,
|
|
654
|
-
),
|
|
655
|
-
view = new DataView(result.buffer);
|
|
656
|
-
view.setUint32(0, records.length, true);
|
|
657
|
-
let at = 4;
|
|
658
|
-
for (const r of records) {
|
|
659
|
-
result.set(r.bytes, at);
|
|
660
|
-
at += r.bytes.length;
|
|
661
|
-
}
|
|
662
|
-
result.set(info.footer, at);
|
|
663
|
-
const all = boundsUnion(records);
|
|
664
|
-
for (const offset of [0, 28, 56]) {
|
|
665
|
-
const b = offset === 28 ? records[0].bounds : all;
|
|
666
|
-
floats(result, at + offset, b.min);
|
|
667
|
-
floats(result, at + offset + 12, b.max);
|
|
668
|
-
}
|
|
669
|
-
authoredAssemblyRecords(result);
|
|
670
|
-
return result;
|
|
671
|
-
}
|
|
672
|
-
function findName(bytes, name) {
|
|
673
|
-
outer: for (let i = 4; i + name.length <= bytes.length; i++) {
|
|
674
|
-
for (let k = 0; k < name.length; k++)
|
|
675
|
-
if (bytes[i + k] !== name[k]) continue outer;
|
|
676
|
-
return i;
|
|
677
|
-
}
|
|
678
|
-
fail('missing generated mesh name');
|
|
679
|
-
}
|