Skip to content

Commit 51d6edb

Browse files
authored
Merge pull request #1575 from pixiv/fix-combine-skeletons-2
fix: Fix combineSkeletons
2 parents f2775db + 5cd45ac commit 51d6edb

File tree

1 file changed

+51
-60
lines changed

1 file changed

+51
-60
lines changed

packages/three-vrm/src/VRMUtils/combineSkeletons.ts

+51-60
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,43 @@ export function combineSkeletons(root: THREE.Object3D): void {
6565
}
6666

6767
// prepare new skeletons for each group, and bind them to the meshes
68-
for (const { boneInverseMap, meshes } of groups) {
68+
69+
// the condition to use the same skin index attribute:
70+
// - the same skin index attribute
71+
// - and the skeleton is same
72+
// - and the bone set is same
73+
const cache = new Map<string, THREE.BufferAttribute | THREE.InterleavedBufferAttribute>();
74+
const skinIndexDispatcher = new ObjectIndexDispatcher<THREE.BufferAttribute | THREE.InterleavedBufferAttribute>();
75+
const skeletonDispatcher = new ObjectIndexDispatcher<THREE.Skeleton>();
76+
const boneDispatcher = new ObjectIndexDispatcher<THREE.Bone>();
77+
78+
for (const group of groups) {
79+
const { boneInverseMap, meshes } = group;
80+
6981
// create a new skeleton
7082
const newBones = Array.from(boneInverseMap.keys());
7183
const newBoneInverses = Array.from(boneInverseMap.values());
7284
const newSkeleton = new THREE.Skeleton(newBones, newBoneInverses);
73-
74-
// collect skin index attributes and corresponding bone arrays
75-
const skinIndexBonesPairSet = collectSkinIndexAttrs(meshes);
85+
const skeletonKey = skeletonDispatcher.getOrCreate(newSkeleton);
7686

7787
// remap skin index attribute
78-
for (const [skinIndexAttr, bones] of skinIndexBonesPairSet) {
79-
remapSkinIndexAttribute(skinIndexAttr, bones, newBones);
88+
for (const mesh of meshes) {
89+
const skinIndexAttr = mesh.geometry.getAttribute('skinIndex');
90+
const skinIndexKey = skinIndexDispatcher.getOrCreate(skinIndexAttr);
91+
92+
const bones = mesh.skeleton.bones;
93+
const bonesKey = bones.map((bone) => boneDispatcher.getOrCreate(bone)).join(',');
94+
95+
const key = `${skinIndexKey};${skeletonKey};${bonesKey}`;
96+
let newSkinIndexAttr = cache.get(key);
97+
98+
if (newSkinIndexAttr == null) {
99+
newSkinIndexAttr = skinIndexAttr.clone();
100+
remapSkinIndexAttribute(newSkinIndexAttr, bones, newBones);
101+
cache.set(key, newSkinIndexAttr);
102+
}
103+
104+
mesh.geometry.setAttribute('skinIndex', newSkinIndexAttr);
80105
}
81106

82107
// bind the new skeleton to the meshes
@@ -189,6 +214,13 @@ function boneInverseMapIsMergeable(
189214
return true;
190215
}
191216

217+
/**
218+
* Remap the skin index attribute from old bones to new bones.
219+
* This function modifies the given attribute in place.
220+
* @param attribute The skin index attribute to remap
221+
* @param oldBones The bone array that the attribute is currently using
222+
* @param newBones The bone array that the attribute will be using
223+
*/
192224
function remapSkinIndexAttribute(
193225
attribute: THREE.BufferAttribute | THREE.InterleavedBufferAttribute,
194226
oldBones: THREE.Bone[],
@@ -236,63 +268,22 @@ function matrixEquals(a: THREE.Matrix4, b: THREE.Matrix4, tolerance?: number) {
236268
return true;
237269
}
238270

239-
/**
240-
* Check if the contents of two arrays are equal.
241-
*/
242-
function arrayEquals<T>(a: T[], b: T[]): boolean {
243-
if (a.length !== b.length) {
244-
return false;
245-
}
246-
247-
return a.every((v, i) => v === b[i]);
248-
}
249-
250-
/**
251-
* Collect skin index attributes and corresponding bone arrays from the given skinned meshes.
252-
* If a skin index attribute is shared among different bone sets, clone the attribute.
253-
*/
254-
function collectSkinIndexAttrs(
255-
meshes: Iterable<THREE.SkinnedMesh>,
256-
): Set<[THREE.BufferAttribute | THREE.InterleavedBufferAttribute, THREE.Bone[]]> {
257-
const skinIndexBonesPairSet = new Set<[THREE.BufferAttribute | THREE.InterleavedBufferAttribute, THREE.Bone[]]>();
258-
259-
// Collect skin index attributes
260-
// skinIndex attribute might be shared among different bone sets
261-
// If there are multiple bone sets that share the same skinIndex attribute, clone the attribute
262-
const skinIndexNewSkinIndexBonesMapMap = new Map<
263-
THREE.BufferAttribute | THREE.InterleavedBufferAttribute,
264-
Map<THREE.BufferAttribute | THREE.InterleavedBufferAttribute, THREE.Bone[]>
265-
>();
271+
class ObjectIndexDispatcher<T> {
272+
private _objectIndexMap = new Map<T, number>();
273+
private _index = 0;
266274

267-
for (const mesh of meshes) {
268-
const skinIndexAttr = mesh.geometry.getAttribute('skinIndex');
269-
270-
// Get or create a map for the skin index attribute
271-
let newSkinIndexBonesMap = skinIndexNewSkinIndexBonesMapMap.get(skinIndexAttr);
272-
if (newSkinIndexBonesMap == null) {
273-
// Create a new map for the skin index attribute and register the bone array
274-
newSkinIndexBonesMap = new Map();
275-
skinIndexNewSkinIndexBonesMapMap.set(skinIndexAttr, newSkinIndexBonesMap);
276-
newSkinIndexBonesMap.set(skinIndexAttr, mesh.skeleton.bones);
277-
skinIndexBonesPairSet.add([skinIndexAttr, mesh.skeleton.bones]);
278-
continue;
279-
}
275+
public get(obj: T): number | undefined {
276+
return this._objectIndexMap.get(obj);
277+
}
280278

281-
// Check if the bone set is already registered
282-
// If the bone set is already registered, reuse the skin index attribute
283-
let newSkinIndexAttr = Array.from(newSkinIndexBonesMap).find(([_, bones]) =>
284-
arrayEquals(bones, mesh.skeleton.bones),
285-
)?.[0];
286-
287-
// If there is no matching bone set, clone the skin index attribute
288-
if (newSkinIndexAttr == null) {
289-
newSkinIndexAttr = skinIndexAttr.clone();
290-
newSkinIndexBonesMap.set(newSkinIndexAttr, mesh.skeleton.bones);
291-
skinIndexBonesPairSet.add([newSkinIndexAttr, mesh.skeleton.bones]);
279+
public getOrCreate(obj: T): number {
280+
let index = this._objectIndexMap.get(obj);
281+
if (index == null) {
282+
index = this._index;
283+
this._objectIndexMap.set(obj, index);
284+
this._index++;
292285
}
293286

294-
mesh.geometry.setAttribute('skinIndex', newSkinIndexAttr);
287+
return index;
295288
}
296-
297-
return skinIndexBonesPairSet;
298289
}

0 commit comments

Comments
 (0)