(function (global, factory) { if (typeof define === "function" && define.amd) { define(["exports", "three"], factory); } else if (typeof exports !== "undefined") { factory(exports, require("three")); } else { var mod = { exports: {} }; factory(mod.exports, global.three); global.SkeletonUtils = mod.exports; } })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports, _three) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.clone = clone; _exports.findBoneTrackData = findBoneTrackData; _exports.getBoneByName = getBoneByName; _exports.getBones = getBones; _exports.getEqualsBonesNames = getEqualsBonesNames; _exports.getHelperFromSkeleton = getHelperFromSkeleton; _exports.getNearestBone = getNearestBone; _exports.getSkeletonOffsets = getSkeletonOffsets; _exports.renameBones = renameBones; _exports.retarget = retarget; _exports.retargetClip = retargetClip; function retarget(target, source) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var pos = new _three.Vector3(), quat = new _three.Quaternion(), scale = new _three.Vector3(), bindBoneMatrix = new _three.Matrix4(), relativeMatrix = new _three.Matrix4(), globalMatrix = new _three.Matrix4(); options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true; options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true; options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false; options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false; options.hip = options.hip !== undefined ? options.hip : 'hip'; options.names = options.names || {}; var sourceBones = source.isObject3D ? source.skeleton.bones : getBones(source), bones = target.isObject3D ? target.skeleton.bones : getBones(target); var bindBones, bone, name, boneTo, bonesPosition; // reset bones if (target.isObject3D) { target.skeleton.pose(); } else { options.useTargetMatrix = true; options.preserveMatrix = false; } if (options.preservePosition) { bonesPosition = []; for (var i = 0; i < bones.length; i++) { bonesPosition.push(bones[i].position.clone()); } } if (options.preserveMatrix) { // reset matrix target.updateMatrixWorld(); target.matrixWorld.identity(); // reset children matrix for (var _i = 0; _i < target.children.length; ++_i) { target.children[_i].updateMatrixWorld(true); } } if (options.offsets) { bindBones = []; for (var _i2 = 0; _i2 < bones.length; ++_i2) { bone = bones[_i2]; name = options.names[bone.name] || bone.name; if (options.offsets && options.offsets[name]) { bone.matrix.multiply(options.offsets[name]); bone.matrix.decompose(bone.position, bone.quaternion, bone.scale); bone.updateMatrixWorld(); } bindBones.push(bone.matrixWorld.clone()); } } for (var _i3 = 0; _i3 < bones.length; ++_i3) { bone = bones[_i3]; name = options.names[bone.name] || bone.name; boneTo = getBoneByName(name, sourceBones); globalMatrix.copy(bone.matrixWorld); if (boneTo) { boneTo.updateMatrixWorld(); if (options.useTargetMatrix) { relativeMatrix.copy(boneTo.matrixWorld); } else { relativeMatrix.copy(target.matrixWorld).invert(); relativeMatrix.multiply(boneTo.matrixWorld); } // ignore scale to extract rotation scale.setFromMatrixScale(relativeMatrix); relativeMatrix.scale(scale.set(1 / scale.x, 1 / scale.y, 1 / scale.z)); // apply to global matrix globalMatrix.makeRotationFromQuaternion(quat.setFromRotationMatrix(relativeMatrix)); if (target.isObject3D) { var boneIndex = bones.indexOf(bone), wBindMatrix = bindBones ? bindBones[boneIndex] : bindBoneMatrix.copy(target.skeleton.boneInverses[boneIndex]).invert(); globalMatrix.multiply(wBindMatrix); } globalMatrix.copyPosition(relativeMatrix); } if (bone.parent && bone.parent.isBone) { bone.matrix.copy(bone.parent.matrixWorld).invert(); bone.matrix.multiply(globalMatrix); } else { bone.matrix.copy(globalMatrix); } if (options.preserveHipPosition && name === options.hip) { bone.matrix.setPosition(pos.set(0, bone.position.y, 0)); } bone.matrix.decompose(bone.position, bone.quaternion, bone.scale); bone.updateMatrixWorld(); } if (options.preservePosition) { for (var _i4 = 0; _i4 < bones.length; ++_i4) { bone = bones[_i4]; name = options.names[bone.name] || bone.name; if (name !== options.hip) { bone.position.copy(bonesPosition[_i4]); } } } if (options.preserveMatrix) { // restore matrix target.updateMatrixWorld(true); } } function retargetClip(target, source, clip) { var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false; options.fps = options.fps !== undefined ? options.fps : 30; options.names = options.names || []; if (!source.isObject3D) { source = getHelperFromSkeleton(source); } var numFrames = Math.round(clip.duration * (options.fps / 1000) * 1000), delta = 1 / options.fps, convertedTracks = [], mixer = new _three.AnimationMixer(source), bones = getBones(target.skeleton), boneDatas = []; var positionOffset, bone, boneTo, boneData, name; mixer.clipAction(clip).play(); mixer.update(0); source.updateMatrixWorld(); for (var i = 0; i < numFrames; ++i) { var time = i * delta; retarget(target, source, options); for (var j = 0; j < bones.length; ++j) { name = options.names[bones[j].name] || bones[j].name; boneTo = getBoneByName(name, source.skeleton); if (boneTo) { bone = bones[j]; boneData = boneDatas[j] = boneDatas[j] || { bone: bone }; if (options.hip === name) { if (!boneData.pos) { boneData.pos = { times: new Float32Array(numFrames), values: new Float32Array(numFrames * 3) }; } if (options.useFirstFramePosition) { if (i === 0) { positionOffset = bone.position.clone(); } bone.position.sub(positionOffset); } boneData.pos.times[i] = time; bone.position.toArray(boneData.pos.values, i * 3); } if (!boneData.quat) { boneData.quat = { times: new Float32Array(numFrames), values: new Float32Array(numFrames * 4) }; } boneData.quat.times[i] = time; bone.quaternion.toArray(boneData.quat.values, i * 4); } } mixer.update(delta); source.updateMatrixWorld(); } for (var _i5 = 0; _i5 < boneDatas.length; ++_i5) { boneData = boneDatas[_i5]; if (boneData) { if (boneData.pos) { convertedTracks.push(new _three.VectorKeyframeTrack('.bones[' + boneData.bone.name + '].position', boneData.pos.times, boneData.pos.values)); } convertedTracks.push(new _three.QuaternionKeyframeTrack('.bones[' + boneData.bone.name + '].quaternion', boneData.quat.times, boneData.quat.values)); } } mixer.uncacheAction(clip); return new _three.AnimationClip(clip.name, -1, convertedTracks); } function getHelperFromSkeleton(skeleton) { var source = new _three.SkeletonHelper(skeleton.bones[0]); source.skeleton = skeleton; return source; } function getSkeletonOffsets(target, source) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var targetParentPos = new _three.Vector3(), targetPos = new _three.Vector3(), sourceParentPos = new _three.Vector3(), sourcePos = new _three.Vector3(), targetDir = new _three.Vector2(), sourceDir = new _three.Vector2(); options.hip = options.hip !== undefined ? options.hip : 'hip'; options.names = options.names || {}; if (!source.isObject3D) { source = getHelperFromSkeleton(source); } var nameKeys = Object.keys(options.names), nameValues = Object.values(options.names), sourceBones = source.isObject3D ? source.skeleton.bones : getBones(source), bones = target.isObject3D ? target.skeleton.bones : getBones(target), offsets = []; var bone, boneTo, name, i; target.skeleton.pose(); for (i = 0; i < bones.length; ++i) { bone = bones[i]; name = options.names[bone.name] || bone.name; boneTo = getBoneByName(name, sourceBones); if (boneTo && name !== options.hip) { var boneParent = getNearestBone(bone.parent, nameKeys), boneToParent = getNearestBone(boneTo.parent, nameValues); boneParent.updateMatrixWorld(); boneToParent.updateMatrixWorld(); targetParentPos.setFromMatrixPosition(boneParent.matrixWorld); targetPos.setFromMatrixPosition(bone.matrixWorld); sourceParentPos.setFromMatrixPosition(boneToParent.matrixWorld); sourcePos.setFromMatrixPosition(boneTo.matrixWorld); targetDir.subVectors(new _three.Vector2(targetPos.x, targetPos.y), new _three.Vector2(targetParentPos.x, targetParentPos.y)).normalize(); sourceDir.subVectors(new _three.Vector2(sourcePos.x, sourcePos.y), new _three.Vector2(sourceParentPos.x, sourceParentPos.y)).normalize(); var laterialAngle = targetDir.angle() - sourceDir.angle(); var offset = new _three.Matrix4().makeRotationFromEuler(new _three.Euler(0, 0, laterialAngle)); bone.matrix.multiply(offset); bone.matrix.decompose(bone.position, bone.quaternion, bone.scale); bone.updateMatrixWorld(); offsets[name] = offset; } } return offsets; } function renameBones(skeleton, names) { var bones = getBones(skeleton); for (var i = 0; i < bones.length; ++i) { var bone = bones[i]; if (names[bone.name]) { bone.name = names[bone.name]; } } return this; } function getBones(skeleton) { return Array.isArray(skeleton) ? skeleton : skeleton.bones; } function getBoneByName(name, skeleton) { for (var i = 0, bones = getBones(skeleton); i < bones.length; i++) { if (name === bones[i].name) return bones[i]; } } function getNearestBone(bone, names) { while (bone.isBone) { if (names.indexOf(bone.name) !== -1) { return bone; } bone = bone.parent; } } function findBoneTrackData(name, tracks) { var regexp = /\[(.*)\]\.(.*)/, result = { name: name }; for (var i = 0; i < tracks.length; ++i) { // 1 is track name // 2 is track type var trackData = regexp.exec(tracks[i].name); if (trackData && name === trackData[1]) { result[trackData[2]] = i; } } return result; } function getEqualsBonesNames(skeleton, targetSkeleton) { var sourceBones = getBones(skeleton), targetBones = getBones(targetSkeleton), bones = []; search: for (var i = 0; i < sourceBones.length; i++) { var boneName = sourceBones[i].name; for (var j = 0; j < targetBones.length; j++) { if (boneName === targetBones[j].name) { bones.push(boneName); continue search; } } } return bones; } function clone(source) { var sourceLookup = new Map(); var cloneLookup = new Map(); var clone = source.clone(); parallelTraverse(source, clone, function (sourceNode, clonedNode) { sourceLookup.set(clonedNode, sourceNode); cloneLookup.set(sourceNode, clonedNode); }); clone.traverse(function (node) { if (!node.isSkinnedMesh) return; var clonedMesh = node; var sourceMesh = sourceLookup.get(node); var sourceBones = sourceMesh.skeleton.bones; clonedMesh.skeleton = sourceMesh.skeleton.clone(); clonedMesh.bindMatrix.copy(sourceMesh.bindMatrix); clonedMesh.skeleton.bones = sourceBones.map(function (bone) { return cloneLookup.get(bone); }); clonedMesh.bind(clonedMesh.skeleton, clonedMesh.bindMatrix); }); return clone; } function parallelTraverse(a, b, callback) { callback(a, b); for (var i = 0; i < a.children.length; i++) { parallelTraverse(a.children[i], b.children[i], callback); } } });