i was able to fix it by using reference points on the mesh.
class SkeletonAlignment {
constructor(mesh, skeletonDataUrl, vertexDataUrl) {
this.mesh = mesh;
this.skeletonDataUrl = skeletonDataUrl; //rig data you generate in blender
this.vertexDataUrl = vertexDataUrl; //vertex group data found in makehuman directory
this.skeletonData = null;
this.vertexData = null;
}
async loadJSONFiles() {
try {
const [skeletonResponse, vertexResponse] = await Promise.all([
fetch(this.skeletonDataUrl),
fetch(this.vertexDataUrl),
]);
this.skeletonData = await skeletonResponse.json();
this.vertexData = await vertexResponse.json();
} catch (error) {
console.error("Error loading JSON files:", error.message);
}
}
// put single values in array, flatten those retrieved from vertex data file
toArray(value) {
return Array.isArray(value) ? value : [value];
}
flattenArray(value) {
return Array.isArray(value) && Array.isArray(value[0]) ? value.flat() : this.toArray(value);
}
getJointPositions(boneName) {
const boneData = this.skeletonData.bones[boneName];
if (!boneData) {
console.warn(`Bone data not found for: ${boneName}`);
return null;
}
// three strategies for each bone: cube, mean, or vertex.
const headVertices = boneData.head.cube_name
? this.flattenArray(this.vertexData[boneData.head.cube_name])
: this.toArray(boneData.head.vertex_indices || boneData.head.vertex_index);
const tailVertices = boneData.tail.cube_name
? this.flattenArray(this.vertexData[boneData.tail.cube_name])
: this.toArray(boneData.tail?.vertex_indices || boneData.tail?.vertex_index);
return {
bone: boneName,
head: boneData.head.cube_name || boneData.head.strategy,
tail: boneData.tail.cube_name || boneData.tail.strategy,
headVertices,
tailVertices,
headPosition: this.calculateAveragePosition(headVertices),
tailPosition: this.calculateAveragePosition(tailVertices),
};
}
calculateAveragePosition(vertices) {
if (!vertices[0]) {
console.warn("invalid or empty vertices array", vertices);
return null;
}
if (vertices.length === 1) {
const tempVec = new THREE.Vector3();
this.mesh.getVertexPosition(vertices[0], tempVec);
return tempVec;
}
const [start, end] = vertices;
const tempVec = new THREE.Vector3();
let sum = new THREE.Vector3();
let count = 0;
for (let i = start; i <= end; i++) {
this.mesh.getVertexPosition(i, tempVec);
sum.add(tempVec);
count++;
}
return sum.divideScalar(count);
}
updateJointPositions() {
const skeleton = this.mesh.skeleton;
const jointPos = skeleton.bones.map((bone) => {
if (bone.userData.name === "neutral_bone") {
//console.warn(`Skipping neutral bone`);
return null;
}
const position = this.getJointPositions(bone.userData.name);
if (!position) {
console.error(`Invalid joint position for bone: ${bone.userData.name}`);
}
return position;
});
skeleton.bones.forEach((bone, i) => {
if (bone.userData.name === "neutral_bone" || !jointPos[i].headPosition) {
return;
}
const localVec = new THREE.Vector3();
const parentBone = bone.parent;
const parentMatrixWorld = parentBone.matrixWorld.clone().invert();
if (!jointPos[i].headPosition) return;
localVec.copy(jointPos[i].headPosition).applyMatrix4(parentMatrixWorld);
bone.position.copy(localVec);
bone.updateMatrixWorld();
});
skeleton.calculateInverses();
this.mesh.updateMatrixWorld();
}
}
when i call the updateJointPositions function with dat gui’s onChange function all bones move to their reference positions on the mesh.