three-vat 1.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -4
- package/dist/carrier-BXaLAPRO.d.ts +515 -0
- package/dist/chunk-3PWAY6MD.js +211 -0
- package/dist/chunk-I4STYOD5.js +53 -0
- package/dist/index.d.ts +113 -14
- package/dist/index.js +622 -68
- package/dist/tsl.d.ts +82 -47
- package/dist/tsl.js +176 -40
- package/dist/webgl.d.ts +31 -26
- package/dist/webgl.js +295 -35
- package/package.json +20 -14
- package/dist/chunk-SXTVASKG.js +0 -40
- package/dist/instance-playback-BrGBIKLe.d.ts +0 -109
package/dist/index.js
CHANGED
|
@@ -1,7 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { LoopMode, MAX_TEXTURE_SIZE, makeVATTexture, RIG_TEXELS_PER_SLOT, RIG_TEXELS, LIBRARY_PLAYBACK_DEFAULTS, FORWARD_ONLY_REASON, INFINITE_REPETITIONS } from './chunk-3PWAY6MD.js';
|
|
2
|
+
export { EndMode, INFINITE_REPETITIONS, LoopMode, MAX_FADE_DURATION, MAX_TEXTURE_SIZE, createVATPlaybackTexture, endsAt, makeVATTexture, resolveVATFrame, setVATInstance } from './chunk-3PWAY6MD.js';
|
|
3
|
+
import { LoopRepeat, LoopOnce, LoopPingPong, Matrix4, AnimationMixer, Box3, Vector4, Vector3, Sphere, Quaternion, BufferGeometry, BufferAttribute, AdditiveAnimationBlendMode, PropertyBinding } from 'three';
|
|
3
4
|
|
|
4
|
-
var
|
|
5
|
+
var LOOP_MODES = /* @__PURE__ */ new Map([
|
|
6
|
+
[LoopRepeat, LoopMode.Repeat],
|
|
7
|
+
[LoopOnce, LoopMode.Once],
|
|
8
|
+
[LoopPingPong, LoopMode.PingPong]
|
|
9
|
+
]);
|
|
10
|
+
function isAction(input) {
|
|
11
|
+
return typeof input.getClip === "function";
|
|
12
|
+
}
|
|
13
|
+
function resolveAnimation(input) {
|
|
14
|
+
if (!isAction(input)) return { clip: input, defaults: { ...LIBRARY_PLAYBACK_DEFAULTS } };
|
|
15
|
+
const clip = input.getClip();
|
|
16
|
+
const name = clip.name || "(unnamed)";
|
|
17
|
+
const cannotBlend = (field, value) => new Error(
|
|
18
|
+
`three-vat: action for clip "${name}" has ${field} ${value}; that blends several actions at once, which a single baked band cannot represent \u2014 bake the clips separately and crossfade between them (three-vat#30), or reset the action before baking`
|
|
19
|
+
);
|
|
20
|
+
if (input.weight !== 1) throw cannotBlend("weight", String(input.weight));
|
|
21
|
+
if (input.blendMode === AdditiveAnimationBlendMode) throw cannotBlend("blendMode", "additive");
|
|
22
|
+
if (input.timeScale < 0) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`three-vat: action for clip "${name}" has timeScale ${input.timeScale}; ${FORWARD_ONLY_REASON}.`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const loopMode = LOOP_MODES.get(input.loop);
|
|
28
|
+
if (loopMode === void 0) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`three-vat: action for clip "${name}" has an unrecognised loop mode ${input.loop}; expected THREE.LoopRepeat, LoopOnce or LoopPingPong`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
clip,
|
|
35
|
+
defaults: {
|
|
36
|
+
loopMode,
|
|
37
|
+
// three's `repetitions` defaults to Infinity and `LoopOnce` ignores it
|
|
38
|
+
// outright — so a one-shot is one play whatever the field says, and an
|
|
39
|
+
// endless count becomes the sentinel a Float32Array can carry.
|
|
40
|
+
repetitions: loopMode === LoopMode.Once ? 1 : Number.isFinite(input.repetitions) ? input.repetitions : INFINITE_REPETITIONS,
|
|
41
|
+
// Not read off the action (see the rule above), so a bake clamps whichever
|
|
42
|
+
// input it was handed. Uniform across loop modes on purpose: an instance
|
|
43
|
+
// may override a repeating clip's `loopMode` to `Once` and inherit this
|
|
44
|
+
// field, so there is no mode whose end mode is safely unreachable and
|
|
45
|
+
// could hold a different answer. Rewind is per instance:
|
|
46
|
+
// `{ ..., endMode: EndMode.Rewind }` (#43, ADR-0017).
|
|
47
|
+
endMode: LIBRARY_PLAYBACK_DEFAULTS.endMode,
|
|
48
|
+
speed: input.timeScale
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function morphCountOf({ mesh, morphPos, morphNrm }) {
|
|
53
|
+
const influences = mesh.morphTargetInfluences;
|
|
54
|
+
return influences ? Math.min(influences.length, Math.max(morphPos?.length ?? 0, morphNrm?.length ?? 0)) : 0;
|
|
55
|
+
}
|
|
5
56
|
function asAttribute(value, mesh, name) {
|
|
6
57
|
if (!(value instanceof BufferAttribute)) {
|
|
7
58
|
throw new Error(
|
|
@@ -44,6 +95,8 @@ function collectParts(root) {
|
|
|
44
95
|
baseNrm: asAttribute(geometry.attributes.normal, mesh, "normal"),
|
|
45
96
|
isSkinned: !!geometry.attributes.skinWeight && !!skinned.skeleton,
|
|
46
97
|
skeleton: skinned.skeleton,
|
|
98
|
+
pose: void 0,
|
|
99
|
+
// assigned below, once the distinct rigs are known
|
|
47
100
|
skinIndex: geometry.attributes.skinIndex,
|
|
48
101
|
skinWeight: geometry.attributes.skinWeight,
|
|
49
102
|
morphPos: geometry.morphAttributes.position,
|
|
@@ -62,20 +115,20 @@ function collectParts(root) {
|
|
|
62
115
|
function mergeGeometry(parts, restMatrices, total) {
|
|
63
116
|
const position = new Float32Array(total * 3);
|
|
64
117
|
const normal = new Float32Array(total * 3);
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const indices = [];
|
|
70
|
-
const groups = [];
|
|
118
|
+
const want = optionalAttributes(parts);
|
|
119
|
+
const uv = want.uv ? new Float32Array(total * 2) : null;
|
|
120
|
+
const color = want.color ? new Float32Array(total * 3) : null;
|
|
121
|
+
const tangent = want.tangent ? new Float32Array(total * 4) : null;
|
|
71
122
|
const _v = new Vector3();
|
|
72
123
|
const _n = new Vector3();
|
|
124
|
+
const _t = new Vector3();
|
|
73
125
|
parts.forEach((part, pi) => {
|
|
74
126
|
const m = restMatrices[pi];
|
|
75
127
|
const geometry = part.mesh.geometry;
|
|
76
128
|
const start = part.vertexStart;
|
|
77
129
|
const srcUV = uv ? asAttribute(geometry.attributes.uv, part.mesh, "uv") : null;
|
|
78
130
|
const srcColor = color ? asAttribute(geometry.attributes.color, part.mesh, "color") : null;
|
|
131
|
+
const srcTangent = tangent ? geometry.attributes.tangent : null;
|
|
79
132
|
for (let v = 0; v < part.vertexCount; v++) {
|
|
80
133
|
_v.fromBufferAttribute(part.basePos, v).applyMatrix4(m);
|
|
81
134
|
_n.fromBufferAttribute(part.baseNrm, v).transformDirection(m);
|
|
@@ -95,9 +148,42 @@ function mergeGeometry(parts, restMatrices, total) {
|
|
|
95
148
|
color[o3 + 1] = srcColor.getY(v);
|
|
96
149
|
color[o3 + 2] = srcColor.getZ(v);
|
|
97
150
|
}
|
|
151
|
+
if (tangent && srcTangent) {
|
|
152
|
+
_t.fromBufferAttribute(srcTangent, v).transformDirection(m);
|
|
153
|
+
const o4 = (start + v) * 4;
|
|
154
|
+
tangent[o4] = _t.x;
|
|
155
|
+
tangent[o4 + 1] = _t.y;
|
|
156
|
+
tangent[o4 + 2] = _t.z;
|
|
157
|
+
tangent[o4 + 3] = srcTangent.getW(v);
|
|
158
|
+
}
|
|
98
159
|
}
|
|
160
|
+
});
|
|
161
|
+
const merged = new BufferGeometry();
|
|
162
|
+
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
163
|
+
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
164
|
+
if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
|
|
165
|
+
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
166
|
+
if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
|
|
167
|
+
indexParts(merged, parts);
|
|
168
|
+
return merged;
|
|
169
|
+
}
|
|
170
|
+
function optionalAttributes(parts) {
|
|
171
|
+
return {
|
|
172
|
+
uv: parts.every((p) => !!p.mesh.geometry.attributes.uv),
|
|
173
|
+
color: parts.every((p) => !!p.mesh.geometry.attributes.color),
|
|
174
|
+
tangent: parts.every((p) => {
|
|
175
|
+
const t = p.mesh.geometry.attributes.tangent;
|
|
176
|
+
return t instanceof BufferAttribute && t.itemSize === 4;
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function indexParts(merged, parts) {
|
|
181
|
+
const indices = [];
|
|
182
|
+
const groups = [];
|
|
183
|
+
for (const part of parts) {
|
|
184
|
+
const start = part.vertexStart;
|
|
99
185
|
const groupStart = indices.length;
|
|
100
|
-
const index = geometry.index;
|
|
186
|
+
const index = part.mesh.geometry.index;
|
|
101
187
|
if (index) {
|
|
102
188
|
for (let i = 0; i < index.count; i++) indices.push(start + index.getX(i));
|
|
103
189
|
} else {
|
|
@@ -113,32 +199,34 @@ function mergeGeometry(parts, restMatrices, total) {
|
|
|
113
199
|
materialIndex: part.materialIndex
|
|
114
200
|
});
|
|
115
201
|
}
|
|
116
|
-
}
|
|
117
|
-
const merged = new BufferGeometry();
|
|
118
|
-
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
119
|
-
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
120
|
-
if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
|
|
121
|
-
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
202
|
+
}
|
|
122
203
|
merged.setIndex(indices);
|
|
123
204
|
for (const g of groups) merged.addGroup(g.start, g.count, g.materialIndex);
|
|
124
|
-
return merged;
|
|
125
205
|
}
|
|
126
|
-
function
|
|
206
|
+
function frameCountsFor(clips, fps, maxTextureSize) {
|
|
207
|
+
const frameCounts = clips.map((c) => Math.max(2, Math.round(c.duration * fps)));
|
|
208
|
+
const totalFrames = frameCounts.reduce((a, b) => a + b, 0);
|
|
209
|
+
if (totalFrames > maxTextureSize) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`three-vat: totalFrames ${totalFrames} exceeds maxTextureSize ${maxTextureSize}; lower fps or bake fewer clips`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return { frameCounts, totalFrames };
|
|
215
|
+
}
|
|
216
|
+
function bakeVAT(root, animations, options = {}) {
|
|
217
|
+
const { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE, bakeNormals = true, encoding = "delta" } = options;
|
|
218
|
+
const resolved = animations.map(resolveAnimation);
|
|
219
|
+
const clips = resolved.map((a) => a.clip);
|
|
127
220
|
root.updateMatrixWorld(true);
|
|
128
221
|
const parts = collectParts(root);
|
|
222
|
+
if (encoding === "rig") return bakeRig(root, parts, resolved, { fps, maxTextureSize });
|
|
129
223
|
const vertexCount = parts.reduce((n, p) => n + p.vertexCount, 0);
|
|
130
224
|
if (vertexCount > maxTextureSize) {
|
|
131
225
|
throw new Error(
|
|
132
226
|
`three-vat: vertexCount ${vertexCount} exceeds maxTextureSize ${maxTextureSize}; row wrapping is not implemented`
|
|
133
227
|
);
|
|
134
228
|
}
|
|
135
|
-
const frameCounts
|
|
136
|
-
const totalFrames = frameCounts.reduce((a, b) => a + b, 0);
|
|
137
|
-
if (totalFrames > maxTextureSize) {
|
|
138
|
-
throw new Error(
|
|
139
|
-
`three-vat: totalFrames ${totalFrames} exceeds maxTextureSize ${maxTextureSize}; lower fps or bake fewer clips`
|
|
140
|
-
);
|
|
141
|
-
}
|
|
229
|
+
const { frameCounts, totalFrames } = frameCountsFor(clips, fps, maxTextureSize);
|
|
142
230
|
const rootInverse = root.matrixWorld.clone().invert();
|
|
143
231
|
const restMatrices = parts.map(
|
|
144
232
|
(p) => new Matrix4().multiplyMatrices(rootInverse, p.mesh.matrixWorld)
|
|
@@ -146,13 +234,13 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
146
234
|
const geometry = mergeGeometry(parts, restMatrices, vertexCount);
|
|
147
235
|
const mergedBase = geometry.attributes.position;
|
|
148
236
|
const posData = new Float32Array(vertexCount * totalFrames * 4);
|
|
149
|
-
const nrmData = new Float32Array(vertexCount * totalFrames * 4);
|
|
237
|
+
const nrmData = bakeNormals ? new Float32Array(vertexCount * totalFrames * 4) : null;
|
|
238
|
+
const bakeNormal = nrmData !== null;
|
|
150
239
|
const mixer = new AnimationMixer(root);
|
|
151
240
|
const bounds = new Box3();
|
|
152
241
|
const clipTable = [];
|
|
153
242
|
const _si = new Vector4();
|
|
154
243
|
const _sw = new Vector4();
|
|
155
|
-
const _bone = new Matrix4();
|
|
156
244
|
const _acc = new Matrix4();
|
|
157
245
|
const _skin = new Matrix4();
|
|
158
246
|
const _partMatrix = new Matrix4();
|
|
@@ -162,7 +250,8 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
162
250
|
const _mt = new Vector3();
|
|
163
251
|
const _mb = new Vector3();
|
|
164
252
|
const _mbn = new Vector3();
|
|
165
|
-
const
|
|
253
|
+
const poses = attachPoseBuffers(parts);
|
|
254
|
+
const influencers = bakeNormals ? parts.filter((p) => p.pose).map(influencedBones) : [];
|
|
166
255
|
let warnedNonUniformScale = false;
|
|
167
256
|
let rowOffset = 0;
|
|
168
257
|
clips.forEach((clip, ci) => {
|
|
@@ -174,22 +263,24 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
174
263
|
mixer.setTime(f / frames * clip.duration);
|
|
175
264
|
root.updateMatrixWorld(true);
|
|
176
265
|
const row = rowOffset + f;
|
|
266
|
+
poseSkeletons(poses);
|
|
177
267
|
if (!warnedNonUniformScale) {
|
|
178
|
-
warnedNonUniformScale = warnOnNonUniformBoneScale(influencers
|
|
268
|
+
warnedNonUniformScale = warnOnNonUniformBoneScale(influencers);
|
|
179
269
|
}
|
|
180
270
|
for (const part of parts) {
|
|
181
271
|
_partMatrix.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
|
|
182
272
|
const influences = part.mesh.morphTargetInfluences;
|
|
183
|
-
const { morphPos, morphNrm, morphRelative, isSkinned,
|
|
184
|
-
const
|
|
273
|
+
const { morphPos, morphNrm, morphRelative, isSkinned, skinIndex, skinWeight } = part;
|
|
274
|
+
const boneMatrices = part.pose?.matrices;
|
|
275
|
+
const morphCount = morphCountOf(part);
|
|
185
276
|
for (let v = 0; v < part.vertexCount; v++) {
|
|
186
277
|
const vi = part.vertexStart + v;
|
|
187
278
|
_p.fromBufferAttribute(part.basePos, v);
|
|
188
|
-
_n.fromBufferAttribute(part.baseNrm, v);
|
|
279
|
+
if (bakeNormal) _n.fromBufferAttribute(part.baseNrm, v);
|
|
189
280
|
if (influences && morphCount > 0) {
|
|
190
281
|
if (!morphRelative) {
|
|
191
282
|
_mb.fromBufferAttribute(part.basePos, v);
|
|
192
|
-
_mbn.fromBufferAttribute(part.baseNrm, v);
|
|
283
|
+
if (bakeNormal) _mbn.fromBufferAttribute(part.baseNrm, v);
|
|
193
284
|
}
|
|
194
285
|
for (let t = 0; t < morphCount; t++) {
|
|
195
286
|
const w = influences[t];
|
|
@@ -200,7 +291,7 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
200
291
|
if (!morphRelative) _mt.sub(_mb);
|
|
201
292
|
_p.addScaledVector(_mt, w);
|
|
202
293
|
}
|
|
203
|
-
const targetNrm = morphNrm?.[t];
|
|
294
|
+
const targetNrm = bakeNormal ? morphNrm?.[t] : void 0;
|
|
204
295
|
if (targetNrm) {
|
|
205
296
|
_mt.fromBufferAttribute(targetNrm, v);
|
|
206
297
|
if (!morphRelative) _mt.sub(_mbn);
|
|
@@ -208,26 +299,24 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
208
299
|
}
|
|
209
300
|
}
|
|
210
301
|
}
|
|
211
|
-
if (isSkinned &&
|
|
302
|
+
if (isSkinned && boneMatrices) {
|
|
212
303
|
const skinned = part.mesh;
|
|
213
304
|
_si.fromBufferAttribute(skinIndex, v);
|
|
214
305
|
_sw.fromBufferAttribute(skinWeight, v);
|
|
215
|
-
_acc.elements
|
|
306
|
+
const ae = _acc.elements;
|
|
307
|
+
ae.fill(0);
|
|
216
308
|
for (let i = 0; i < 4; i++) {
|
|
217
309
|
const w = _sw.getComponent(i);
|
|
218
310
|
if (w === 0) continue;
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
const ae = _acc.elements;
|
|
222
|
-
const be = _bone.elements;
|
|
223
|
-
for (let e = 0; e < 16; e++) ae[e] += be[e] * w;
|
|
311
|
+
const b = _si.getComponent(i) * BONE_STRIDE;
|
|
312
|
+
for (let e = 0; e < 16; e++) ae[e] += boneMatrices[b + e] * w;
|
|
224
313
|
}
|
|
225
314
|
_skin.multiplyMatrices(_acc, skinned.bindMatrix).premultiply(skinned.bindMatrixInverse);
|
|
226
315
|
_p.applyMatrix4(_skin);
|
|
227
|
-
_n.transformDirection(_skin);
|
|
316
|
+
if (bakeNormal) _n.transformDirection(_skin);
|
|
228
317
|
}
|
|
229
318
|
_p.applyMatrix4(_partMatrix);
|
|
230
|
-
_n.transformDirection(_partMatrix);
|
|
319
|
+
if (bakeNormal) _n.transformDirection(_partMatrix);
|
|
231
320
|
bounds.expandByPoint(_p);
|
|
232
321
|
_bp.fromBufferAttribute(mergedBase, vi);
|
|
233
322
|
const o = (row * vertexCount + vi) * 4;
|
|
@@ -239,10 +328,12 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
239
328
|
posData[o + 1] = dy;
|
|
240
329
|
posData[o + 2] = dz;
|
|
241
330
|
posData[o + 3] = 1;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
331
|
+
if (bakeNormal) {
|
|
332
|
+
nrmData[o] = _n.x;
|
|
333
|
+
nrmData[o + 1] = _n.y;
|
|
334
|
+
nrmData[o + 2] = _n.z;
|
|
335
|
+
nrmData[o + 3] = 1;
|
|
336
|
+
}
|
|
246
337
|
}
|
|
247
338
|
}
|
|
248
339
|
}
|
|
@@ -253,7 +344,10 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
253
344
|
frames,
|
|
254
345
|
fps: frames / clip.duration,
|
|
255
346
|
duration: clip.duration,
|
|
256
|
-
maxDelta: Math.sqrt(maxDeltaSq)
|
|
347
|
+
maxDelta: Math.sqrt(maxDeltaSq),
|
|
348
|
+
// Declared once, here, rather than repeated at every instance that plays
|
|
349
|
+
// this band. An instance overrides any of them, field by field.
|
|
350
|
+
...resolved[ci].defaults
|
|
257
351
|
});
|
|
258
352
|
rowOffset += frames;
|
|
259
353
|
});
|
|
@@ -266,7 +360,7 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
266
360
|
for (const part of parts) materials[part.materialIndex] = part.material;
|
|
267
361
|
return {
|
|
268
362
|
positionTexture: makeVATTexture(posData, vertexCount, totalFrames),
|
|
269
|
-
normalTexture: makeVATTexture(nrmData, vertexCount, totalFrames),
|
|
363
|
+
normalTexture: nrmData ? makeVATTexture(nrmData, vertexCount, totalFrames) : null,
|
|
270
364
|
clips: clipTable,
|
|
271
365
|
bounds,
|
|
272
366
|
vertexCount,
|
|
@@ -276,6 +370,32 @@ function bakeVAT(root, clips, { fps = 30, maxTextureSize = MAX_TEXTURE_SIZE } =
|
|
|
276
370
|
materials
|
|
277
371
|
};
|
|
278
372
|
}
|
|
373
|
+
var BONE_STRIDE = 16;
|
|
374
|
+
var IDENTITY = /* @__PURE__ */ new Matrix4();
|
|
375
|
+
function attachPoseBuffers(parts) {
|
|
376
|
+
const byRig = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const part of parts) {
|
|
378
|
+
if (!part.isSkinned || !part.skeleton) continue;
|
|
379
|
+
let pose = byRig.get(part.skeleton);
|
|
380
|
+
if (!pose) {
|
|
381
|
+
const matrices = new Float64Array(part.skeleton.bones.length * BONE_STRIDE);
|
|
382
|
+
pose = { skeleton: part.skeleton, matrices };
|
|
383
|
+
byRig.set(part.skeleton, pose);
|
|
384
|
+
}
|
|
385
|
+
part.pose = pose;
|
|
386
|
+
}
|
|
387
|
+
return [...byRig.values()];
|
|
388
|
+
}
|
|
389
|
+
function poseSkeletons(poses) {
|
|
390
|
+
const scratch = new Matrix4();
|
|
391
|
+
for (const { skeleton, matrices } of poses) {
|
|
392
|
+
const { bones, boneInverses } = skeleton;
|
|
393
|
+
for (let b = 0; b < bones.length; b++) {
|
|
394
|
+
scratch.multiplyMatrices(bones[b]?.matrixWorld ?? IDENTITY, boneInverses[b]);
|
|
395
|
+
matrices.set(scratch.elements, b * BONE_STRIDE);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
279
399
|
function influencedBones(part) {
|
|
280
400
|
const used = /* @__PURE__ */ new Set();
|
|
281
401
|
const index = part.skinIndex;
|
|
@@ -285,37 +405,471 @@ function influencedBones(part) {
|
|
|
285
405
|
if (weight.getComponent(v, i) !== 0) used.add(index.getComponent(v, i));
|
|
286
406
|
}
|
|
287
407
|
}
|
|
288
|
-
return {
|
|
408
|
+
return { pose: part.pose, bones: [...used] };
|
|
289
409
|
}
|
|
290
410
|
var SCALE_UNIFORMITY_EPSILON = 1e-4;
|
|
291
|
-
function hasNonUniformScale(
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
411
|
+
function hasNonUniformScale(matrices, b) {
|
|
412
|
+
const o = b * BONE_STRIDE;
|
|
413
|
+
return unevenSquares(
|
|
414
|
+
matrices[o] ** 2 + matrices[o + 1] ** 2 + matrices[o + 2] ** 2,
|
|
415
|
+
matrices[o + 4] ** 2 + matrices[o + 5] ** 2 + matrices[o + 6] ** 2,
|
|
416
|
+
matrices[o + 8] ** 2 + matrices[o + 9] ** 2 + matrices[o + 10] ** 2
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
function unevenSquares(x, y, z) {
|
|
296
420
|
const max = Math.max(x, y, z);
|
|
297
421
|
return max - Math.min(x, y, z) > SCALE_UNIFORMITY_EPSILON * max;
|
|
298
422
|
}
|
|
299
|
-
function warnOnNonUniformBoneScale(influencers
|
|
300
|
-
for (const {
|
|
423
|
+
function warnOnNonUniformBoneScale(influencers) {
|
|
424
|
+
for (const { pose, bones } of influencers) {
|
|
301
425
|
for (const b of bones) {
|
|
302
|
-
|
|
303
|
-
if (!hasNonUniformScale(scratch)) continue;
|
|
426
|
+
if (!hasNonUniformScale(pose.matrices, b)) continue;
|
|
304
427
|
console.warn(
|
|
305
|
-
`three-vat: bone "${skeleton.bones[b].name || "(unnamed)"}" animates with non-uniform scale; baked normals under it are approximate, because linear-blend skinning transforms a normal by the skin matrix rather than its inverse-transpose \u2014 the same shortcut three's own skinning shader takes. Positions are exact.`
|
|
428
|
+
`three-vat: bone "${pose.skeleton.bones[b].name || "(unnamed)"}" animates with non-uniform scale; baked normals under it are approximate, because linear-blend skinning transforms a normal by the skin matrix rather than its inverse-transpose \u2014 the same shortcut three's own skinning shader takes. Positions are exact.`
|
|
306
429
|
);
|
|
307
430
|
return true;
|
|
308
431
|
}
|
|
309
432
|
}
|
|
310
433
|
return false;
|
|
311
434
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
435
|
+
var USE_VERTEX_ENCODING = "bake this subtree with the vertex encoding (the default) instead";
|
|
436
|
+
function placementOf(part, rootInverse, out) {
|
|
437
|
+
out.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
|
|
438
|
+
if (part.pose) out.multiply(part.mesh.bindMatrixInverse);
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
var MATRIX_EPSILON = 1e-6;
|
|
442
|
+
function matricesClose(a, b) {
|
|
443
|
+
const ae = a.elements;
|
|
444
|
+
const be = b.elements;
|
|
445
|
+
for (let e = 0; e < 16; e++) if (Math.abs(ae[e] - be[e]) > MATRIX_EPSILON) return false;
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
function slotLabel(slot) {
|
|
449
|
+
const parts = slot.parts.map(partName).join(", ");
|
|
450
|
+
if (!slot.rig) return `rigid part ${parts}`;
|
|
451
|
+
const { pose, bone } = slot.rig;
|
|
452
|
+
return `bone "${pose.skeleton.bones[bone]?.name || `bone ${bone}`}" of ${parts}`;
|
|
453
|
+
}
|
|
454
|
+
function partName(part) {
|
|
455
|
+
return `"${part.mesh.name || "(unnamed)"}"`;
|
|
456
|
+
}
|
|
457
|
+
function slotReads(slot, bone, boneInverse, bindMatrix, placement) {
|
|
458
|
+
const { rig } = slot;
|
|
459
|
+
return rig !== void 0 && rig.pose.skeleton.bones[rig.bone] === bone && matricesClose(rig.pose.skeleton.boneInverses[rig.bone], boneInverse) && matricesClose(rig.bindMatrix, bindMatrix) && matricesClose(slot.restPlacement, placement);
|
|
460
|
+
}
|
|
461
|
+
function layoutSlots(parts, rootInverse) {
|
|
462
|
+
const slots = [];
|
|
463
|
+
const slotMaps = /* @__PURE__ */ new Map();
|
|
464
|
+
for (const part of parts) {
|
|
465
|
+
const restPlacement = placementOf(part, rootInverse, new Matrix4());
|
|
466
|
+
if (part.pose) {
|
|
467
|
+
const { pose } = part;
|
|
468
|
+
const { bones, boneInverses } = pose.skeleton;
|
|
469
|
+
const bindMatrix = part.mesh.bindMatrix.clone();
|
|
470
|
+
const map = [];
|
|
471
|
+
for (let b = 0; b < bones.length; b++) {
|
|
472
|
+
const bone = bones[b];
|
|
473
|
+
const shared = bone ? slots.findIndex((s) => slotReads(s, bone, boneInverses[b], bindMatrix, restPlacement)) : -1;
|
|
474
|
+
if (shared >= 0) {
|
|
475
|
+
const slot = slots[shared];
|
|
476
|
+
if (!slot.parts.includes(part)) slot.parts.push(part);
|
|
477
|
+
map.push(shared);
|
|
478
|
+
} else {
|
|
479
|
+
map.push(slots.length);
|
|
480
|
+
slots.push({ rig: { pose, bone: b, bindMatrix }, parts: [part], restPlacement });
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
slotMaps.set(part, map);
|
|
484
|
+
} else {
|
|
485
|
+
slotMaps.set(part, [slots.length]);
|
|
486
|
+
slots.push({ rig: void 0, parts: [part], restPlacement });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return { slots, slotMaps };
|
|
490
|
+
}
|
|
491
|
+
function morphTracksOn(mesh, clip, root) {
|
|
492
|
+
const driven = /* @__PURE__ */ new Map();
|
|
493
|
+
for (const track of clip.tracks) {
|
|
494
|
+
const parsed = PropertyBinding.parseTrackName(track.name);
|
|
495
|
+
if (parsed.propertyName !== "morphTargetInfluences") continue;
|
|
496
|
+
if (PropertyBinding.findNode(root, parsed.nodeName) !== mesh) continue;
|
|
497
|
+
const size = track.getValueSize();
|
|
498
|
+
const keys = track.times.length;
|
|
499
|
+
const values = track.values;
|
|
500
|
+
const add = (t, read) => {
|
|
501
|
+
const list = driven.get(t) ?? [];
|
|
502
|
+
for (let k = 0; k < keys; k++) list.push(read(k));
|
|
503
|
+
driven.set(t, list);
|
|
504
|
+
};
|
|
505
|
+
if (parsed.propertyIndex === void 0) {
|
|
506
|
+
for (let t = 0; t < size; t++) add(t, (k) => values[k * size + t]);
|
|
507
|
+
} else {
|
|
508
|
+
const byIndex = Number(parsed.propertyIndex);
|
|
509
|
+
const t = Number.isInteger(byIndex) ? byIndex : mesh.morphTargetDictionary?.[parsed.propertyIndex];
|
|
510
|
+
if (t !== void 0) add(t, (k) => values[k]);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return driven;
|
|
514
|
+
}
|
|
515
|
+
function morphInfluences(part, clips, root) {
|
|
516
|
+
const { mesh } = part;
|
|
517
|
+
const influences = mesh.morphTargetInfluences;
|
|
518
|
+
const count = morphCountOf(part);
|
|
519
|
+
if (!influences || count === 0) return { statics: [], animated: [] };
|
|
520
|
+
const driven = clips.map((clip) => morphTracksOn(mesh, clip, root));
|
|
521
|
+
const statics = [];
|
|
522
|
+
const animated = [];
|
|
523
|
+
for (let t = 0; t < count; t++) {
|
|
524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
525
|
+
const offending = [];
|
|
526
|
+
driven.forEach((tracks, ci) => {
|
|
527
|
+
const values = tracks.get(t);
|
|
528
|
+
if (!values) {
|
|
529
|
+
seen.add(influences[t]);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
offending.push(`"${clips[ci].name}"`);
|
|
533
|
+
for (const value of values) seen.add(value);
|
|
534
|
+
});
|
|
535
|
+
if (seen.size > 1) {
|
|
536
|
+
const name = Object.entries(mesh.morphTargetDictionary ?? {}).find(([, i]) => i === t)?.[0];
|
|
537
|
+
animated.push({ part, target: name ? `"${name}"` : String(t), clips: offending });
|
|
538
|
+
}
|
|
539
|
+
statics.push(seen.values().next().value);
|
|
540
|
+
}
|
|
541
|
+
return { statics, animated };
|
|
542
|
+
}
|
|
543
|
+
function refuseAnimatedMorphs(animated) {
|
|
544
|
+
const byDriver = /* @__PURE__ */ new Map();
|
|
545
|
+
for (const { part, target, clips } of animated) {
|
|
546
|
+
const key = `${target}
|
|
547
|
+
${clips.join("\n")}`;
|
|
548
|
+
const entry = byDriver.get(key) ?? { target, clips, parts: [] };
|
|
549
|
+
if (!entry.parts.includes(part)) entry.parts.push(part);
|
|
550
|
+
byDriver.set(key, entry);
|
|
551
|
+
}
|
|
552
|
+
const offences = [...byDriver.values()].map(({ target, clips, parts }) => {
|
|
553
|
+
const one = clips.length === 1;
|
|
554
|
+
const its = parts.length === 1 ? "its" : "their";
|
|
555
|
+
return `clip${one ? "" : "s"} ${clips.join(", ")} animate${one ? "s" : ""} morph target ${target} of part${parts.length === 1 ? "" : "s"} ${parts.map(partName).join(", ")}, ${its} vertices moving where no bone does`;
|
|
556
|
+
});
|
|
557
|
+
return new Error(
|
|
558
|
+
`three-vat: the rig encoding cannot bake this subtree: ${offences.join("; ")}. A slot stores a rotation, a translation and one scale, not a per-vertex delta \u2014 ${USE_VERTEX_ENCODING}`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
function foldMorphs(part, statics) {
|
|
562
|
+
const { vertexCount, basePos, baseNrm, morphPos, morphNrm, morphRelative } = part;
|
|
563
|
+
const position = new Float32Array(vertexCount * 3);
|
|
564
|
+
const normal = new Float32Array(vertexCount * 3);
|
|
565
|
+
const active = statics.some((w) => w !== 0);
|
|
566
|
+
const _p = new Vector3();
|
|
567
|
+
const _n = new Vector3();
|
|
568
|
+
const _mt = new Vector3();
|
|
569
|
+
const _mb = new Vector3();
|
|
570
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
571
|
+
_p.fromBufferAttribute(basePos, v);
|
|
572
|
+
_n.fromBufferAttribute(baseNrm, v);
|
|
573
|
+
if (active) {
|
|
574
|
+
for (let t = 0; t < statics.length; t++) {
|
|
575
|
+
const w = statics[t];
|
|
576
|
+
if (w === 0) continue;
|
|
577
|
+
const targetPos = morphPos?.[t];
|
|
578
|
+
if (targetPos) {
|
|
579
|
+
_mt.fromBufferAttribute(targetPos, v);
|
|
580
|
+
if (!morphRelative) _mt.sub(_mb.fromBufferAttribute(basePos, v));
|
|
581
|
+
_p.addScaledVector(_mt, w);
|
|
582
|
+
}
|
|
583
|
+
const targetNrm = morphNrm?.[t];
|
|
584
|
+
if (targetNrm) {
|
|
585
|
+
_mt.fromBufferAttribute(targetNrm, v);
|
|
586
|
+
if (!morphRelative) _mt.sub(_mb.fromBufferAttribute(baseNrm, v));
|
|
587
|
+
_n.addScaledVector(_mt, w);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
_n.normalize();
|
|
591
|
+
}
|
|
592
|
+
_p.toArray(position, v * 3);
|
|
593
|
+
_n.toArray(normal, v * 3);
|
|
594
|
+
}
|
|
595
|
+
return { position, normal };
|
|
596
|
+
}
|
|
597
|
+
function scaleSensitiveNodes(slots, root, influenced) {
|
|
598
|
+
const sensitive = /* @__PURE__ */ new Map();
|
|
599
|
+
slots.forEach((slot, i) => {
|
|
600
|
+
if (!influenced[i]) return;
|
|
601
|
+
if (slot.rig) {
|
|
602
|
+
const { bones } = slot.rig.pose.skeleton;
|
|
603
|
+
for (let node = bones[slot.rig.bone] ?? null; node && bones.includes(node); node = node.parent) {
|
|
604
|
+
sensitive.set(node, slot);
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
for (let node = slot.parts[0].mesh; node && node !== root; node = node.parent) {
|
|
608
|
+
sensitive.set(node, slot);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
return sensitive;
|
|
613
|
+
}
|
|
614
|
+
function refuseAnimatedNonUniformScale(slots, clips, root, influenced) {
|
|
615
|
+
const sensitive = scaleSensitiveNodes(slots, root, influenced);
|
|
616
|
+
for (const clip of clips) {
|
|
617
|
+
for (const track of clip.tracks) {
|
|
618
|
+
const parsed = PropertyBinding.parseTrackName(track.name);
|
|
619
|
+
if (parsed.propertyName !== "scale" || track.getValueSize() !== 3) continue;
|
|
620
|
+
const node = PropertyBinding.findNode(root, parsed.nodeName);
|
|
621
|
+
const reached = node && sensitive.get(node);
|
|
622
|
+
if (!reached) continue;
|
|
623
|
+
const values = track.values;
|
|
624
|
+
for (let k = 0; k < track.times.length; k++) {
|
|
625
|
+
const o = k * 3;
|
|
626
|
+
if (unevenSquares(values[o] ** 2, values[o + 1] ** 2, values[o + 2] ** 2)) {
|
|
627
|
+
throw nonUniformScale(slotLabel(reached), clip);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function nonUniformScale(label, clip) {
|
|
634
|
+
return new Error(
|
|
635
|
+
`three-vat: ${label} animates with non-uniform scale in clip "${clip.name}", which the rig encoding cannot store \u2014 a slot is a rotation, a translation and one scale. ${USE_VERTEX_ENCODING}, or author it with a uniform scale.`
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
function bakeRig(root, parts, resolved, { fps, maxTextureSize }) {
|
|
639
|
+
const clips = resolved.map((a) => a.clip);
|
|
640
|
+
const rootInverse = root.matrixWorld.clone().invert();
|
|
641
|
+
const poses = attachPoseBuffers(parts);
|
|
642
|
+
const { slots, slotMaps } = layoutSlots(parts, rootInverse);
|
|
643
|
+
const slotCount = slots.length;
|
|
644
|
+
const morphs = parts.map((part) => morphInfluences(part, clips, root));
|
|
645
|
+
const animated = morphs.flatMap((m) => m.animated);
|
|
646
|
+
if (animated.length > 0) throw refuseAnimatedMorphs(animated);
|
|
647
|
+
const rigParts = parts.map((part, i) => ({
|
|
648
|
+
part,
|
|
649
|
+
slotOf: slotMaps.get(part),
|
|
650
|
+
...foldMorphs(part, morphs[i].statics)
|
|
651
|
+
}));
|
|
652
|
+
const width = slotCount * RIG_TEXELS_PER_SLOT;
|
|
653
|
+
if (width > maxTextureSize) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`three-vat: rig texture width ${width} (${slotCount} slots \xD7 ${RIG_TEXELS_PER_SLOT} texels) exceeds maxTextureSize ${maxTextureSize}`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const { frameCounts, totalFrames } = frameCountsFor(clips, fps, maxTextureSize);
|
|
659
|
+
const vertexCount = parts.reduce((n, p) => n + p.vertexCount, 0);
|
|
660
|
+
const geometry = mergeRigGeometry(rigParts, vertexCount);
|
|
661
|
+
const localPosition = geometry.attributes.position.array;
|
|
662
|
+
const skinIndex = geometry.attributes.skinIndex.array;
|
|
663
|
+
const skinWeight = geometry.attributes.skinWeight.array;
|
|
664
|
+
const influenced = new Uint8Array(slotCount);
|
|
665
|
+
for (let i = 0; i < skinWeight.length; i++) {
|
|
666
|
+
if (skinWeight[i] !== 0) influenced[skinIndex[i]] = 1;
|
|
667
|
+
}
|
|
668
|
+
refuseAnimatedNonUniformScale(slots, clips, root, influenced);
|
|
669
|
+
const _p = new Vector3();
|
|
670
|
+
const _slot = new Matrix4();
|
|
671
|
+
const _partMatrix = new Matrix4();
|
|
672
|
+
const placements = new Map(parts.map((part) => [part, new Matrix4()]));
|
|
673
|
+
const _q = new Quaternion();
|
|
674
|
+
const _t = new Vector3();
|
|
675
|
+
const _s = new Vector3();
|
|
676
|
+
const restRoot = new Float64Array(vertexCount * 3);
|
|
677
|
+
for (const part of parts) {
|
|
678
|
+
_partMatrix.multiplyMatrices(rootInverse, part.mesh.matrixWorld);
|
|
679
|
+
for (let v = 0; v < part.vertexCount; v++) {
|
|
680
|
+
_p.fromBufferAttribute(part.basePos, v).applyMatrix4(_partMatrix);
|
|
681
|
+
_p.toArray(restRoot, (part.vertexStart + v) * 3);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const data = new Float32Array(width * totalFrames * 4);
|
|
685
|
+
const slotMatrices = new Float64Array(slotCount * BONE_STRIDE);
|
|
686
|
+
const previous = new Float64Array(slotCount * 4);
|
|
687
|
+
const mixer = new AnimationMixer(root);
|
|
688
|
+
const bounds = new Box3();
|
|
689
|
+
const clipTable = [];
|
|
690
|
+
let minX = Infinity;
|
|
691
|
+
let minY = Infinity;
|
|
692
|
+
let minZ = Infinity;
|
|
693
|
+
let maxX = -Infinity;
|
|
694
|
+
let maxY = -Infinity;
|
|
695
|
+
let maxZ = -Infinity;
|
|
696
|
+
let rowOffset = 0;
|
|
697
|
+
clips.forEach((clip, ci) => {
|
|
698
|
+
const frames = frameCounts[ci];
|
|
699
|
+
const action = mixer.clipAction(clip);
|
|
700
|
+
action.play();
|
|
701
|
+
let maxDeltaSq = 0;
|
|
702
|
+
for (let f = 0; f < frames; f++) {
|
|
703
|
+
mixer.setTime(f / frames * clip.duration);
|
|
704
|
+
root.updateMatrixWorld(true);
|
|
705
|
+
const row = rowOffset + f;
|
|
706
|
+
poseSkeletons(poses);
|
|
707
|
+
for (const [part, placement] of placements) placementOf(part, rootInverse, placement);
|
|
708
|
+
for (let slot = 0; slot < slotCount; slot++) {
|
|
709
|
+
const { rig, parts: readers } = slots[slot];
|
|
710
|
+
const lead = readers[0];
|
|
711
|
+
const placement = placements.get(lead);
|
|
712
|
+
for (let pi = 1; pi < readers.length; pi++) {
|
|
713
|
+
const other = readers[pi];
|
|
714
|
+
if (!matricesClose(placement, placements.get(other))) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
`three-vat: parts ${partName(lead)} and ${partName(other)} read the same slots but move apart in clip "${clip.name}"; the rig encoding gives them one set of slots, which cannot place them differently \u2014 ${USE_VERTEX_ENCODING}`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (rig) {
|
|
721
|
+
_slot.fromArray(rig.pose.matrices, rig.bone * BONE_STRIDE).multiply(rig.bindMatrix).premultiply(placement);
|
|
722
|
+
} else {
|
|
723
|
+
_slot.copy(placement);
|
|
724
|
+
}
|
|
725
|
+
slotMatrices.set(_slot.elements, slot * BONE_STRIDE);
|
|
726
|
+
if (influenced[slot] && hasNonUniformScale(slotMatrices, slot)) {
|
|
727
|
+
throw nonUniformScale(slotLabel(slots[slot]), clip);
|
|
728
|
+
}
|
|
729
|
+
_slot.decompose(_t, _q, _s);
|
|
730
|
+
const p4 = slot * 4;
|
|
731
|
+
if (previous[p4] * _q.x + previous[p4 + 1] * _q.y + previous[p4 + 2] * _q.z + previous[p4 + 3] * _q.w < 0) {
|
|
732
|
+
_q.set(-_q.x, -_q.y, -_q.z, -_q.w);
|
|
733
|
+
}
|
|
734
|
+
previous[p4] = _q.x;
|
|
735
|
+
previous[p4 + 1] = _q.y;
|
|
736
|
+
previous[p4 + 2] = _q.z;
|
|
737
|
+
previous[p4 + 3] = _q.w;
|
|
738
|
+
const o = (row * width + slot * RIG_TEXELS_PER_SLOT) * 4;
|
|
739
|
+
const rotation = o + RIG_TEXELS.rotation * 4;
|
|
740
|
+
data[rotation] = _q.x;
|
|
741
|
+
data[rotation + 1] = _q.y;
|
|
742
|
+
data[rotation + 2] = _q.z;
|
|
743
|
+
data[rotation + 3] = _q.w;
|
|
744
|
+
const placementTexel = o + RIG_TEXELS.placement * 4;
|
|
745
|
+
data[placementTexel] = _t.x;
|
|
746
|
+
data[placementTexel + 1] = _t.y;
|
|
747
|
+
data[placementTexel + 2] = _t.z;
|
|
748
|
+
data[placementTexel + 3] = _s.x;
|
|
749
|
+
}
|
|
750
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
751
|
+
const o3 = v * 3;
|
|
752
|
+
const o4 = v * 4;
|
|
753
|
+
const x = localPosition[o3];
|
|
754
|
+
const y = localPosition[o3 + 1];
|
|
755
|
+
const z = localPosition[o3 + 2];
|
|
756
|
+
let px = 0;
|
|
757
|
+
let py = 0;
|
|
758
|
+
let pz = 0;
|
|
759
|
+
for (let i = 0; i < 4; i++) {
|
|
760
|
+
const w = skinWeight[o4 + i];
|
|
761
|
+
if (w === 0) continue;
|
|
762
|
+
const m = skinIndex[o4 + i] * BONE_STRIDE;
|
|
763
|
+
px += w * (slotMatrices[m] * x + slotMatrices[m + 4] * y + slotMatrices[m + 8] * z + slotMatrices[m + 12]);
|
|
764
|
+
py += w * (slotMatrices[m + 1] * x + slotMatrices[m + 5] * y + slotMatrices[m + 9] * z + slotMatrices[m + 13]);
|
|
765
|
+
pz += w * (slotMatrices[m + 2] * x + slotMatrices[m + 6] * y + slotMatrices[m + 10] * z + slotMatrices[m + 14]);
|
|
766
|
+
}
|
|
767
|
+
if (px < minX) minX = px;
|
|
768
|
+
if (px > maxX) maxX = px;
|
|
769
|
+
if (py < minY) minY = py;
|
|
770
|
+
if (py > maxY) maxY = py;
|
|
771
|
+
if (pz < minZ) minZ = pz;
|
|
772
|
+
if (pz > maxZ) maxZ = pz;
|
|
773
|
+
const dx = px - restRoot[o3];
|
|
774
|
+
const dy = py - restRoot[o3 + 1];
|
|
775
|
+
const dz = pz - restRoot[o3 + 2];
|
|
776
|
+
const deltaSq = dx * dx + dy * dy + dz * dz;
|
|
777
|
+
if (deltaSq > maxDeltaSq) maxDeltaSq = deltaSq;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
action.stop();
|
|
781
|
+
clipTable.push({
|
|
782
|
+
name: clip.name,
|
|
783
|
+
startFrame: rowOffset,
|
|
784
|
+
frames,
|
|
785
|
+
fps: frames / clip.duration,
|
|
786
|
+
duration: clip.duration,
|
|
787
|
+
maxDelta: Math.sqrt(maxDeltaSq),
|
|
788
|
+
...resolved[ci].defaults
|
|
789
|
+
});
|
|
790
|
+
rowOffset += frames;
|
|
791
|
+
});
|
|
792
|
+
mixer.stopAllAction();
|
|
793
|
+
mixer.setTime(0);
|
|
794
|
+
root.updateMatrixWorld(true);
|
|
795
|
+
bounds.min.set(minX, minY, minZ);
|
|
796
|
+
bounds.max.set(maxX, maxY, maxZ);
|
|
797
|
+
geometry.boundingBox = bounds.clone();
|
|
798
|
+
geometry.boundingSphere = bounds.getBoundingSphere(new Sphere());
|
|
799
|
+
const materials = [];
|
|
800
|
+
for (const part of parts) materials[part.materialIndex] = part.material;
|
|
801
|
+
return {
|
|
802
|
+
encoding: "rig",
|
|
803
|
+
rigTexture: makeVATTexture(data, width, totalFrames),
|
|
804
|
+
slotCount,
|
|
805
|
+
clips: clipTable,
|
|
806
|
+
bounds,
|
|
807
|
+
vertexCount,
|
|
808
|
+
totalFrames,
|
|
809
|
+
geometry,
|
|
810
|
+
materials
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function mergeRigGeometry(rigParts, total) {
|
|
814
|
+
const parts = rigParts.map((r) => r.part);
|
|
815
|
+
const position = new Float32Array(total * 3);
|
|
816
|
+
const normal = new Float32Array(total * 3);
|
|
817
|
+
const slotIndex = new Uint16Array(total * 4);
|
|
818
|
+
const slotWeight = new Float32Array(total * 4);
|
|
819
|
+
const want = optionalAttributes(parts);
|
|
820
|
+
const uv = want.uv ? new Float32Array(total * 2) : null;
|
|
821
|
+
const color = want.color ? new Float32Array(total * 3) : null;
|
|
822
|
+
const tangent = want.tangent ? new Float32Array(total * 4) : null;
|
|
823
|
+
for (const { part, slotOf, position: restPos, normal: restNrm } of rigParts) {
|
|
824
|
+
const geometry = part.mesh.geometry;
|
|
825
|
+
const start = part.vertexStart;
|
|
826
|
+
const srcIndex = part.pose ? part.skinIndex : null;
|
|
827
|
+
const srcWeight = part.pose ? part.skinWeight : null;
|
|
828
|
+
const srcUV = uv ? asAttribute(geometry.attributes.uv, part.mesh, "uv") : null;
|
|
829
|
+
const srcColor = color ? asAttribute(geometry.attributes.color, part.mesh, "color") : null;
|
|
830
|
+
const srcTangent = tangent ? geometry.attributes.tangent : null;
|
|
831
|
+
position.set(restPos, start * 3);
|
|
832
|
+
normal.set(restNrm, start * 3);
|
|
833
|
+
for (let v = 0; v < part.vertexCount; v++) {
|
|
834
|
+
const vi = start + v;
|
|
835
|
+
const o3 = vi * 3;
|
|
836
|
+
const o4 = vi * 4;
|
|
837
|
+
if (srcIndex && srcWeight) {
|
|
838
|
+
for (let i = 0; i < 4; i++) {
|
|
839
|
+
slotIndex[o4 + i] = slotOf[srcIndex.getComponent(v, i)];
|
|
840
|
+
slotWeight[o4 + i] = srcWeight.getComponent(v, i);
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
slotIndex[o4] = slotOf[0];
|
|
844
|
+
slotWeight[o4] = 1;
|
|
845
|
+
}
|
|
846
|
+
if (uv && srcUV) {
|
|
847
|
+
uv[vi * 2] = srcUV.getX(v);
|
|
848
|
+
uv[vi * 2 + 1] = srcUV.getY(v);
|
|
849
|
+
}
|
|
850
|
+
if (color && srcColor) {
|
|
851
|
+
color[o3] = srcColor.getX(v);
|
|
852
|
+
color[o3 + 1] = srcColor.getY(v);
|
|
853
|
+
color[o3 + 2] = srcColor.getZ(v);
|
|
854
|
+
}
|
|
855
|
+
if (tangent && srcTangent) {
|
|
856
|
+
tangent[o4] = srcTangent.getX(v);
|
|
857
|
+
tangent[o4 + 1] = srcTangent.getY(v);
|
|
858
|
+
tangent[o4 + 2] = srcTangent.getZ(v);
|
|
859
|
+
tangent[o4 + 3] = srcTangent.getW(v);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const merged = new BufferGeometry();
|
|
864
|
+
merged.setAttribute("position", new BufferAttribute(position, 3));
|
|
865
|
+
merged.setAttribute("normal", new BufferAttribute(normal, 3));
|
|
866
|
+
merged.setAttribute("skinIndex", new BufferAttribute(slotIndex, 4));
|
|
867
|
+
merged.setAttribute("skinWeight", new BufferAttribute(slotWeight, 4));
|
|
868
|
+
if (uv) merged.setAttribute("uv", new BufferAttribute(uv, 2));
|
|
869
|
+
if (color) merged.setAttribute("color", new BufferAttribute(color, 3));
|
|
870
|
+
if (tangent) merged.setAttribute("tangent", new BufferAttribute(tangent, 4));
|
|
871
|
+
indexParts(merged, parts);
|
|
872
|
+
return merged;
|
|
319
873
|
}
|
|
320
874
|
|
|
321
|
-
export {
|
|
875
|
+
export { bakeVAT };
|