diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone.meta b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone.meta new file mode 100644 index 000000000..c90652755 --- /dev/null +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 11bcd309fb41db841bca0a02919c3778 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx new file mode 100644 index 000000000..594b8d763 --- /dev/null +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1ae6753a30d246c31540f319ff36dbf23e0987a33fa5cf87e6f0f177b15693 +size 1417900 diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx.meta b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx.meta new file mode 100644 index 000000000..1a1994bba --- /dev/null +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx.meta @@ -0,0 +1,525 @@ +fileFormatVersion: 2 +guid: 08341f64ea9feaf448b13cb539f2c4c8 +ModelImporter: + serializedVersion: 24200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 3 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + generateMeshLods: 0 + meshLodGenerationFlags: 0 + maximumMeshLod: -1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 0 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: + - name: BaseAvatar - OptiTrack 5 bone(Clone) + parentName: + position: {x: 0, y: 0, z: 0} + rotation: {x: 0, y: 0, z: 0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_Root + parentName: BaseAvatar - OptiTrack 5 bone(Clone) + position: {x: -0, y: 0, z: 0} + rotation: {x: -0.7071068, y: 0, z: -0, w: 0.7071067} + scale: {x: 1, y: 1, z: 1} + - name: 001_Hips + parentName: 001_Root + position: {x: -0, y: -0.00000014102463, z: 0.865603} + rotation: {x: 0.7071068, y: 0, z: -0, w: 0.7071067} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftUpLeg + parentName: 001_Hips + position: {x: -0.0897235, y: 3.3639758e-14, z: 0} + rotation: {x: -7.105428e-14, y: 0, z: -0, w: 1} + scale: {x: 0.99999994, y: 0.99999994, z: 0.99999994} + - name: 001_LeftLeg + parentName: 001_LeftUpLeg + position: {x: -0.000000007450581, y: -0.365626, z: 2.8421713e-14} + rotation: {x: -2.1316282e-14, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftFoot + parentName: 001_LeftLeg + position: {x: -0.000000007450581, y: -0.42819893, z: -9.7699635e-15} + rotation: {x: 2.842171e-14, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftToeBase + parentName: 001_LeftFoot + position: {x: -0, y: -0.058320336, z: 0.134585} + rotation: {x: 0.00000016933137, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LToeEnd + parentName: 001_LeftToeBase + position: {x: 0.000000007450581, y: 0.000000020170326, z: 0.043999992} + rotation: {x: -0.0000000846657, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_LToeEnd_end + parentName: 001_LToeEnd + position: {x: -0, y: 0.043999996, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightUpLeg + parentName: 001_Hips + position: {x: 0.0897235, y: 3.3639758e-14, z: 0} + rotation: {x: -7.1054274e-14, y: 0, z: -0, w: 1} + scale: {x: 0.99999994, y: 1, z: 0.99999994} + - name: 001_RightLeg + parentName: 001_RightUpLeg + position: {x: 0.000000007450581, y: -0.36562604, z: 2.1316285e-14} + rotation: {x: -2.4868997e-14, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_RightFoot + parentName: 001_RightLeg + position: {x: -0, y: -0.42819893, z: -1.332268e-14} + rotation: {x: 2.4868996e-14, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightToeBase + parentName: 001_RightFoot + position: {x: -0, y: -0.058320336, z: 0.134585} + rotation: {x: 0.00000016933137, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RToeEnd + parentName: 001_RightToeBase + position: {x: -0.000000007450581, y: 0.000000020170326, z: 0.043999992} + rotation: {x: -0.0000000846657, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_RToeEnd_end + parentName: 001_RToeEnd + position: {x: -0, y: 0.043999996, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_Spine + parentName: 001_Hips + position: {x: -0, y: 0.03325814, z: 0} + rotation: {x: 0.0888559, y: 0, z: -0, w: 0.9960445} + scale: {x: 1, y: 0.99999994, z: 0.99999994} + - name: 001_Spine1 + parentName: 001_Spine + position: {x: -0, y: 0.059864596, z: 0} + rotation: {x: 0.0035778934, y: 0, z: -0, w: 0.9999936} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_Spine2 + parentName: 001_Spine1 + position: {x: -0, y: 0.066516146, z: 0.000000011175871} + rotation: {x: -0.09237056, y: 0, z: -0, w: 0.9957247} + scale: {x: 1, y: 1, z: 1} + - name: 001_Spine3 + parentName: 001_Spine2 + position: {x: -0, y: 0.07815656, z: 7.712515e-10} + rotation: {x: -0.049721405, y: 0, z: -0, w: 0.99876314} + scale: {x: 0.99999994, y: 0.99999994, z: 1} + - name: 001_Spine4 + parentName: 001_Spine3 + position: {x: -0, y: 0.17460515, z: -0.0000000069849193} + rotation: {x: -0.024432134, y: 0, z: -0, w: 0.9997015} + scale: {x: 1, y: 1, z: 0.9999999} + - name: 001_LeftShoulder + parentName: 001_Spine4 + position: {x: -0.0358696, y: 0.045248758, z: -0.0049734665} + rotation: {x: -0.00000016391277, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftArm + parentName: 001_LeftShoulder + position: {x: -0.134977, y: 0.000000013154931, z: 0.000000019557774} + rotation: {x: 0.000000059604645, y: 0, z: -0, w: 1} + scale: {x: 0.99999994, y: 1, z: 1} + - name: 001_LeftForeArm + parentName: 001_LeftArm + position: {x: -0.273732, y: 0.000000011816155, z: -0.000000006519258} + rotation: {x: 0.0000000037252903, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHand + parentName: 001_LeftForeArm + position: {x: -0.19546905, y: 0.000000011990778, z: -0.000000008381903} + rotation: {x: -0.00000008195639, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandIndex1 + parentName: 001_LeftHand + position: {x: -0.07671362, y: 0.000000024912879, z: 0.029833097} + rotation: {x: 0.087155744, y: 0, z: -0, w: 0.9961947} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandIndex2 + parentName: 001_LeftHandIndex1 + position: {x: -0.042618692, y: -0.0000000088475645, z: -0.0000000018626451} + rotation: {x: 0.000000058207664, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_LeftHandIndex3 + parentName: 001_LeftHandIndex2 + position: {x: -0.021309257, y: -0.0000000060535967, z: -9.313226e-10} + rotation: {x: -0.000000041443855, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LIndex3End + parentName: 001_LeftHandIndex3 + position: {x: -0.023440301, y: -0.000000007916242, z: -9.313226e-10} + rotation: {x: -4.656613e-10, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LIndex3End_end + parentName: 001_LIndex3End + position: {x: -0, y: 0.023440227, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandMiddle1 + parentName: 001_LeftHand + position: {x: -0.07671362, y: 0.00000009732321, z: 0.009887521} + rotation: {x: 0.000000067055225, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_LeftHandMiddle2 + parentName: 001_LeftHandMiddle1 + position: {x: -0.046880484, y: -0.000000022235326, z: 0.000000010244548} + rotation: {x: -0.000000040978193, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandMiddle3 + parentName: 001_LeftHandMiddle2 + position: {x: -0.025571287, y: 0.00000009633368, z: 0.0000000121071935} + rotation: {x: -0.000000048428774, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LMiddle3End + parentName: 001_LeftHandMiddle3 + position: {x: -0.028128326, y: 0.00000009551877, z: 0.000000014901161} + rotation: {x: 0.0000000037252903, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LMiddle3End_end + parentName: 001_LMiddle3End + position: {x: -0, y: 0.028128315, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandPinky1 + parentName: 001_LeftHand + position: {x: -0.06818998, y: 0.00000012246892, z: -0.0298331} + rotation: {x: -0.08715575, y: 0, z: -0, w: 0.9961947} + scale: {x: 1, y: 1.0000001, z: 0.99999994} + - name: 001_LeftHandPinky2 + parentName: 001_LeftHandPinky1 + position: {x: -0.03409493, y: 0.00000008754432, z: -0.000000044703484} + rotation: {x: 0.0000005662441, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandPinky3 + parentName: 001_LeftHandPinky2 + position: {x: -0.017047465, y: 0.00000005122274, z: -0.0000000037252903} + rotation: {x: -0.000000692904, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LPinky3End + parentName: 001_LeftHandPinky3 + position: {x: -0.018752158, y: 0.00000009872019, z: -0.000000048428774} + rotation: {x: 0.000000014901161, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LPinky3End_end + parentName: 001_LPinky3End + position: {x: -0, y: 0.018752106, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandRing1 + parentName: 001_LeftHand + position: {x: -0.07245177, y: -0.00000006821938, z: -0.009887559} + rotation: {x: 0.000000115484, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandRing2 + parentName: 001_LeftHandRing1 + position: {x: -0.04261875, y: 0.000000047963113, z: 0.000000013038516} + rotation: {x: -0.0000000037252903, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandRing3 + parentName: 001_LeftHandRing2 + position: {x: -0.021309197, y: 0.000000047730282, z: 0.000000031664968} + rotation: {x: 0.00000033527613, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LRing3End + parentName: 001_LeftHandRing3 + position: {x: -0.023440242, y: 0.00000015622936, z: 0.000000018626451} + rotation: {x: -0.000000007450581, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LRing3End_end + parentName: 001_LRing3End + position: {x: -0, y: 0.02344036, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandThumb1 + parentName: 001_LeftHand + position: {x: -0.018752277, y: -0.021309385, z: 0.029833077} + rotation: {x: 0.4072523, y: 0.34298575, z: 0.23624216, w: 0.81283206} + scale: {x: 1, y: 1, z: 1} + - name: 001_LeftHandThumb2 + parentName: 001_LeftHandThumb1 + position: {x: -0.02832238, y: 0.000000027939677, z: -0.000000055909595} + rotation: {x: 0, y: 0, z: 0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_LeftHandThumb3 + parentName: 001_LeftHandThumb2 + position: {x: -0.024207503, y: -0.000000015943257, z: -0.0000000066128223} + rotation: {x: 0.0000014528632, y: 0.00000007765939, z: 9.01501e-14, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LThumb3End + parentName: 001_LeftHandThumb3 + position: {x: -0.026628096, y: -0.0000000408308, z: -0.000000017800573} + rotation: {x: 0.000000014901161, y: 1.4210855e-13, z: -1.4654944e-14, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_LThumb3End_end + parentName: 001_LThumb3End + position: {x: -0, y: 0.026628118, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_Neck + parentName: 001_Spine4 + position: {x: -0, y: 0.10975203, z: 0.010808894} + rotation: {x: 0.13785088, y: 0, z: -0, w: 0.990453} + scale: {x: 1, y: 0.9999999, z: 0.9999999} + - name: 001_Neck1 + parentName: 001_Neck + position: {x: -0, y: 0.0740802, z: -9.313226e-10} + rotation: {x: 0.000000052154068, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_Head + parentName: 001_Neck1 + position: {x: -0, y: 0.07597939, z: -0.000000016763806} + rotation: {x: -0.10018798, y: 0, z: -0, w: 0.99496853} + scale: {x: 1, y: 1, z: 0.99999994} + - name: 001_HeadEnd + parentName: 001_Head + position: {x: -0, y: 0.22000016, z: -0.000000004656613} + rotation: {x: -0.000000018626451, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_HeadEnd_end + parentName: 001_HeadEnd + position: {x: -0, y: 0.22000012, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightShoulder + parentName: 001_Spine4 + position: {x: 0.0358696, y: 0.045248758, z: -0.0049734665} + rotation: {x: -0.00000016391277, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightArm + parentName: 001_RightShoulder + position: {x: 0.134977, y: 0.000000013154931, z: 0.000000019557774} + rotation: {x: 0.000000059604645, y: 0, z: -0, w: 1} + scale: {x: 0.99999994, y: 1, z: 1} + - name: 001_RightForeArm + parentName: 001_RightArm + position: {x: 0.273732, y: 0.000000011816155, z: -0.000000006519258} + rotation: {x: 0.000000007450581, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_RightHand + parentName: 001_RightForeArm + position: {x: 0.19546905, y: 0.00000013131648, z: -0.000000008381903} + rotation: {x: -0.000000078231096, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandIndex1 + parentName: 001_RightHand + position: {x: 0.07671362, y: -0.00000009406358, z: 0.029833067} + rotation: {x: 0.08715574, y: 0, z: -0, w: 0.9961947} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandIndex2 + parentName: 001_RightHandIndex1 + position: {x: 0.04261881, y: -0.0000000088475645, z: 0.0000000027939677} + rotation: {x: 0.000000058673326, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_RightHandIndex3 + parentName: 001_RightHandIndex2 + position: {x: 0.021309316, y: -0.00000012526289, z: 0.000000004656613} + rotation: {x: -0.000000040512532, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RIndex3End + parentName: 001_RightHandIndex3 + position: {x: 0.023440242, y: -0.00000012712553, z: 0.000000004656613} + rotation: {x: 4.656613e-10, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RIndex3End_end + parentName: 001_RIndex3End + position: {x: -0, y: 0.023440227, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandMiddle1 + parentName: 001_RightHand + position: {x: 0.07671362, y: -0.00000002188608, z: 0.009887507} + rotation: {x: 0.000000059604645, y: 0, z: -0, w: 1} + scale: {x: 1, y: 0.99999994, z: 1} + - name: 001_RightHandMiddle2 + parentName: 001_RightHandMiddle1 + position: {x: 0.046880543, y: 0.00000009697396, z: -0.0000000044237822} + rotation: {x: -0.000000052154064, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandMiddle3 + parentName: 001_RightHandMiddle2 + position: {x: 0.025571287, y: -0.00000002287561, z: -0.000000002561137} + rotation: {x: -0.000000052154064, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RMiddle3End + parentName: 001_RightHandMiddle3 + position: {x: 0.028128386, y: -0.000000023690518, z: -0.0000000146683306} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RMiddle3End_end + parentName: 001_RMiddle3End + position: {x: -0, y: 0.028128315, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandPinky1 + parentName: 001_RightHand + position: {x: 0.06818998, y: 0.0000000027939677, z: -0.029833127} + rotation: {x: -0.08715576, y: 0, z: -0, w: 0.9961947} + scale: {x: 1, y: 1.0000001, z: 0.99999994} + - name: 001_RightHandPinky2 + parentName: 001_RightHandPinky1 + position: {x: 0.03409493, y: -0.000000029802322, z: -0.000000011175871} + rotation: {x: 0.0000005736947, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandPinky3 + parentName: 001_RightHandPinky2 + position: {x: 0.017047405, y: 0.000000053085387, z: -0.000000033527613} + rotation: {x: -0.00000068545336, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1.0000001, z: 1.0000001} + - name: 001_RPinky3End + parentName: 001_RightHandPinky3 + position: {x: 0.018752217, y: -0.000000018626451, z: -0.000000022351742} + rotation: {x: 0.000000014901161, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RPinky3End_end + parentName: 001_RPinky3End + position: {x: -0, y: 0.018752106, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandRing1 + parentName: 001_RightHand + position: {x: 0.07245177, y: -0.00000018742867, z: -0.009887574} + rotation: {x: 0.00000010803342, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandRing2 + parentName: 001_RightHandRing1 + position: {x: 0.042618692, y: 0.000000047963113, z: -0.0000000018626451} + rotation: {x: -0.000000007450581, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandRing3 + parentName: 001_RightHandRing2 + position: {x: 0.021309316, y: 0.000000047730282, z: 0.0000000018626451} + rotation: {x: 0.00000032782555, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RRing3End + parentName: 001_RightHandRing3 + position: {x: 0.023440242, y: 0.000000037020072, z: -0.000000013038516} + rotation: {x: -0.000000011175871, y: 0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RRing3End_end + parentName: 001_RRing3End + position: {x: -0, y: 0.02344036, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandThumb1 + parentName: 001_RightHand + position: {x: 0.018752277, y: -0.021309385, z: 0.029833062} + rotation: {x: 0.4072523, y: -0.34298575, z: -0.23624216, w: 0.81283206} + scale: {x: 1, y: 1, z: 1} + - name: 001_RightHandThumb2 + parentName: 001_RightHandThumb1 + position: {x: 0.028322378, y: -0.000000031664968, z: 0.000000053137} + rotation: {x: 0, y: 0, z: 0, w: 1} + scale: {x: 1.0000001, y: 1, z: 1} + - name: 001_RightHandThumb3 + parentName: 001_RightHandThumb2 + position: {x: 0.024207383, y: -0.000000059786174, z: -0.000000082027555} + rotation: {x: 0.0000014454124, y: -0.00000007765938, z: -9.8754345e-14, w: 1} + scale: {x: 1, y: 1.0000001, z: 1.0000001} + - name: 001_RThumb3End + parentName: 001_RightHandThumb3 + position: {x: 0.026628036, y: -0.000000084079474, z: -0.00000003786181} + rotation: {x: -8.285039e-15, y: -1.2789769e-13, z: 5.162537e-15, w: 1} + scale: {x: 1, y: 1, z: 1} + - name: 001_RThumb3End_end + parentName: 001_RThumb3End + position: {x: -0, y: 0.026628118, z: 0} + rotation: {x: 0, y: -0, z: -0, w: 1} + scale: {x: 1, y: 1, z: 1} + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 1 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab new file mode 100644 index 000000000..33f27aa2b --- /dev/null +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a963eb7286e4684c83f0319792bf23bd4aff2a40ac2c20bac8e652d48d10a67 +size 114732 diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab.meta b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab.meta new file mode 100644 index 000000000..c820123ac --- /dev/null +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b1469350b1e555d45a488d3dd26f3f0c +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs index 4e0c940aa..d0c80bfdf 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Editor/OptitrackSkeletonAnimatorEditor.cs @@ -1,9 +1,13 @@ using UnityEditor; using UnityEngine; +using System.Collections.Generic; [CustomEditor(typeof(OptitrackSkeletonAnimator_Mingle))] public class OptitrackSkeletonAnimatorEditor : Editor { + private bool showMappingFoldout = true; + private bool showDebugBonesFoldout = false; + public override void OnInspectorGUI() { DrawDefaultInspector(); @@ -15,7 +19,7 @@ public class OptitrackSkeletonAnimatorEditor : Editor GUI.backgroundColor = skeletonAnimator.isSkeletonFound ? Color.green : Color.red; EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); - GUILayout.Label(skeletonAnimator.isSkeletonFound ? "연결됨" : "연결 안됨", + GUILayout.Label(skeletonAnimator.isSkeletonFound ? "연결됨" : "연결 안됨", new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter }); EditorGUILayout.EndHorizontal(); GUI.backgroundColor = Color.white; @@ -29,6 +33,232 @@ public class OptitrackSkeletonAnimatorEditor : Editor EditorGUILayout.HelpBox("Motive에서 스켈레톤이 활성화되어 있는지 확인해주세요.", MessageType.Warning); } + // ── 본 매핑 도구 ── + GUILayout.Space(10); + EditorGUILayout.LabelField("본 매핑 도구", EditorStyles.boldLabel); + + GUI.backgroundColor = new Color(0.4f, 0.8f, 0.4f); + if (GUILayout.Button("FBX 분석 → 자동 매핑 생성", GUILayout.Height(35))) + { + Undo.RecordObject(skeletonAnimator, "Auto Generate Bone Mappings"); + AutoGenerateMappingsEditor(skeletonAnimator); + EditorUtility.SetDirty(skeletonAnimator); + } + GUI.backgroundColor = Color.white; + + // 매핑 상태 요약 + if (skeletonAnimator.boneMappings != null && skeletonAnimator.boneMappings.Count > 0) + { + int mapped = 0; + int unmapped = 0; + foreach (var m in skeletonAnimator.boneMappings) + { + if (m.isMapped) mapped++; + else unmapped++; + } + + EditorGUILayout.Space(5); + EditorGUILayout.HelpBox( + $"매핑 상태: {mapped}개 성공 / {unmapped}개 실패 (총 {skeletonAnimator.boneMappings.Count}개)", + unmapped > 0 ? MessageType.Warning : MessageType.Info); + + // 매핑 리스트 + EditorGUILayout.Space(5); + showMappingFoldout = EditorGUILayout.Foldout(showMappingFoldout, $"본 매핑 목록 ({skeletonAnimator.boneMappings.Count})", true); + if (showMappingFoldout) + { + EditorGUI.indentLevel++; + for (int i = 0; i < skeletonAnimator.boneMappings.Count; i++) + { + var mapping = skeletonAnimator.boneMappings[i]; + Color labelColor = mapping.isMapped ? Color.green : Color.red; + + EditorGUILayout.BeginHorizontal(); + + var prevColor = GUI.contentColor; + GUI.contentColor = labelColor; + EditorGUILayout.LabelField(mapping.isMapped ? "●" : "○", GUILayout.Width(15)); + GUI.contentColor = prevColor; + + EditorGUILayout.LabelField(mapping.optiTrackBoneName, GUILayout.Width(100)); + EditorGUILayout.LabelField("→", GUILayout.Width(20)); + + // FBX 노드 이름 — 수동 편집 가능 + EditorGUI.BeginChangeCheck(); + string newName = EditorGUILayout.TextField(mapping.fbxNodeName); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(skeletonAnimator, "Edit Bone Mapping"); + mapping.fbxNodeName = newName; + var allTransforms = skeletonAnimator.GetComponentsInChildren(true); + mapping.cachedTransform = null; + mapping.isMapped = false; + foreach (var t in allTransforms) + { + if (t.name == newName) + { + mapping.cachedTransform = t; + mapping.isMapped = true; + break; + } + } + EditorUtility.SetDirty(skeletonAnimator); + } + + // Position / Rotation 토글 + EditorGUI.BeginChangeCheck(); + mapping.applyPosition = EditorGUILayout.ToggleLeft("P", mapping.applyPosition, GUILayout.Width(30)); + mapping.applyRotation = EditorGUILayout.ToggleLeft("R", mapping.applyRotation, GUILayout.Width(30)); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(skeletonAnimator, "Toggle Bone Apply"); + EditorUtility.SetDirty(skeletonAnimator); + } + + EditorGUILayout.EndHorizontal(); + } + EditorGUI.indentLevel--; + } + } + + EditorGUILayout.Space(5); + GUI.backgroundColor = new Color(0.9f, 0.4f, 0.4f); + if (GUILayout.Button("매핑 초기화")) + { + Undo.RecordObject(skeletonAnimator, "Clear Bone Mappings"); + skeletonAnimator.boneMappings.Clear(); + EditorUtility.SetDirty(skeletonAnimator); + } + GUI.backgroundColor = Color.white; + + // ── 런타임 수신 본 디버그 ── + GUILayout.Space(10); + EditorGUILayout.LabelField("Motive 수신 본 디버그 (런타임)", EditorStyles.boldLabel); + + if (!Application.isPlaying) + { + EditorGUILayout.HelpBox("플레이 모드에서 Motive 연결 후 확인할 수 있습니다.", MessageType.Info); + } + else if (skeletonAnimator.debugReceivedBoneNames.Count == 0) + { + EditorGUILayout.HelpBox("아직 본 데이터를 수신하지 못했습니다. Motive 스켈레톤이 활성화되어 있는지 확인해주세요.", MessageType.Warning); + } + else + { + EditorGUILayout.LabelField($"수신된 본 수: {skeletonAnimator.debugReceivedBoneCount}"); + + showDebugBonesFoldout = EditorGUILayout.Foldout(showDebugBonesFoldout, $"Motive 본 목록 ({skeletonAnimator.debugReceivedBoneNames.Count})", true); + if (showDebugBonesFoldout) + { + EditorGUI.indentLevel++; + foreach (var boneName in skeletonAnimator.debugReceivedBoneNames) + { + EditorGUILayout.LabelField(boneName); + } + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(3); + if (GUILayout.Button("Console에 전체 본 목록 출력")) + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine($"=== Motive 수신 본 목록 ({skeletonAnimator.SkeletonAssetName}) ==="); + sb.AppendLine($"총 {skeletonAnimator.debugReceivedBoneCount}개"); + sb.AppendLine(); + foreach (var bn in skeletonAnimator.debugReceivedBoneNames) + { + sb.AppendLine(bn); + } + Debug.Log(sb.ToString()); + } + } + + // 플레이 모드일 때 자동 갱신 + if (Application.isPlaying) + Repaint(); + GUILayout.Space(10); } -} \ No newline at end of file + + private void AutoGenerateMappingsEditor(OptitrackSkeletonAnimator_Mingle script) + { + script.boneMappings.Clear(); + + var allTransforms = script.GetComponentsInChildren(true); + var transformMap = new Dictionary(); + foreach (var t in allTransforms) + { + if (!transformMap.ContainsKey(t.name)) + transformMap[t.name] = t; + } + + // 접두사 자동 감지 (예: "001_Hips" → "001_") + string prefix = ""; + foreach (var kvp in transformMap) + { + if (kvp.Key.EndsWith("Hips")) + { + int idx = kvp.Key.LastIndexOf("Hips"); + if (idx > 0) + { + prefix = kvp.Key.Substring(0, idx); + break; + } + } + } + + if (!string.IsNullOrEmpty(prefix)) + Debug.Log($"[OptiTrack 매핑] 감지된 접두사: \"{prefix}\""); + + int mapped = 0; + foreach (var kvp in OptitrackSkeletonAnimator_Mingle.DefaultOptiToFbxSuffix) + { + var mapping = new OptiTrackBoneMapping + { + optiTrackBoneName = kvp.Key, + applyPosition = true, + applyRotation = true, + isMapped = false + }; + + // 1순위: 접두사 + FBX 접미사 + string fullName = prefix + kvp.Value; + if (transformMap.TryGetValue(fullName, out Transform found)) + { + mapping.fbxNodeName = fullName; + mapping.cachedTransform = found; + mapping.isMapped = true; + } + // 2순위: 접미사만 + else if (transformMap.TryGetValue(kvp.Value, out Transform found2)) + { + mapping.fbxNodeName = kvp.Value; + mapping.cachedTransform = found2; + mapping.isMapped = true; + } + // 3순위: 끝나는 이름으로 부분 검색 + else + { + foreach (var t in transformMap) + { + if (t.Key.EndsWith(kvp.Value) || t.Key.EndsWith("_" + kvp.Value)) + { + mapping.fbxNodeName = t.Key; + mapping.cachedTransform = t.Value; + mapping.isMapped = true; + break; + } + } + } + + if (!mapping.isMapped) + mapping.fbxNodeName = prefix + kvp.Value + " (미발견)"; + else + mapped++; + + script.boneMappings.Add(mapping); + } + + Debug.Log($"[OptiTrack 매핑] 완료: {mapped}/{script.boneMappings.Count} 본 매핑 성공"); + } +} diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs index 36195d5ca..75cdbc0e7 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs @@ -1,17 +1,21 @@ using System; using System.Collections.Generic; -using Unity.Mathematics; using UnityEngine; using System.Collections; [System.Serializable] -public enum MotionApplicationScope +public class OptiTrackBoneMapping { - All, // 전신 적용 - ExcludeFingersOnly, // 손가락만 제외 - ExcludeHandsAndFingers // 손목 + 손가락 제외 + public string optiTrackBoneName; // OptiTrack 본 이름 (예: "Hip", "Ab") + public string fbxNodeName; // FBX 노드 이름 (예: "001_Hips", "001_Spine") + [HideInInspector] + public Transform cachedTransform; // 런타임 캐시 + public bool applyPosition = true; + public bool applyRotation = true; + public bool isMapped = false; // 매핑 성공 여부 } +[DefaultExecutionOrder(-100)] public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour { [Header("OptiTrack 설정")] @@ -20,232 +24,285 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour [Tooltip("Motive의 스켈레톤 에셋 이름")] public string SkeletonAssetName = "Skeleton1"; - private Animator TargetAnimator; - [Header("모션 적용 범위")] - [Tooltip("모션 캡처 데이터를 적용할 범위 선택")] - public MotionApplicationScope motionScope = MotionApplicationScope.All; + [Header("본 매핑 (OptiTrack → FBX 노드)")] + [Tooltip("자동 매핑 후 수동 조정 가능")] + public List boneMappings = new List(); + + [Header("디버그")] + public bool logUnmappedBones = false; private OptitrackSkeletonDefinition m_skeletonDef; - private Dictionary m_optitrackToHumanBoneMap; - private string previousSkeletonName; [HideInInspector] public bool isSkeletonFound = false; + // 에디터 디버그용 — 런타임에 Motive에서 실제로 수신된 본 목록 + [HideInInspector] + public List debugReceivedBoneNames = new List(); + [HideInInspector] + public int debugReceivedBoneCount = 0; + private float updateInterval = 0.1f; + // 본 ID → 매핑 인덱스 (빠른 룩업) + private Dictionary m_boneIdToMappingIndex = new Dictionary(); + // 본 이름 → Transform 캐시 (전체 하이어라키) + private Dictionary m_allTransforms = new Dictionary(); + + // torn read 방지용 스냅샷 버퍼 + private Dictionary m_snapshotPositions = new Dictionary(); + private Dictionary m_snapshotOrientations = new Dictionary(); + + // OptiTrack 본 이름 → FBX 노드 접미사 기본 매핑 + public static readonly Dictionary DefaultOptiToFbxSuffix = new Dictionary + { + // 몸통 (Motive 5본 스파인 체인) + // Motive: Hip → Ab → Spine2 → Spine3 → Spine4 → Chest → Neck → Neck2 → Head + // FBX: Hips → Spine → Spine1 → Spine2 → Spine3 → Spine4 → Neck → Neck1 → Head + {"Hip", "Hips"}, + {"Ab", "Spine"}, + {"Spine2", "Spine1"}, + {"Spine3", "Spine2"}, + {"Spine4", "Spine3"}, + {"Chest", "Spine4"}, + {"Neck", "Neck"}, + {"Neck2", "Neck1"}, + {"Head", "Head"}, + + // 왼쪽 팔 + {"LShoulder", "LeftShoulder"}, + {"LUArm", "LeftArm"}, + {"LFArm", "LeftForeArm"}, + {"LHand", "LeftHand"}, + + // 오른쪽 팔 + {"RShoulder", "RightShoulder"}, + {"RUArm", "RightArm"}, + {"RFArm", "RightForeArm"}, + {"RHand", "RightHand"}, + + // 왼쪽 다리 + {"LThigh", "LeftUpLeg"}, + {"LShin", "LeftLeg"}, + {"LFoot", "LeftFoot"}, + {"LToe", "LeftToeBase"}, + + // 오른쪽 다리 + {"RThigh", "RightUpLeg"}, + {"RShin", "RightLeg"}, + {"RFoot", "RightFoot"}, + {"RToe", "RightToeBase"}, + + // 왼쪽 손가락 + {"LThumb1", "LeftHandThumb1"}, + {"LThumb2", "LeftHandThumb2"}, + {"LThumb3", "LeftHandThumb3"}, + {"LIndex1", "LeftHandIndex1"}, + {"LIndex2", "LeftHandIndex2"}, + {"LIndex3", "LeftHandIndex3"}, + {"LMiddle1", "LeftHandMiddle1"}, + {"LMiddle2", "LeftHandMiddle2"}, + {"LMiddle3", "LeftHandMiddle3"}, + {"LRing1", "LeftHandRing1"}, + {"LRing2", "LeftHandRing2"}, + {"LRing3", "LeftHandRing3"}, + {"LPinky1", "LeftHandPinky1"}, + {"LPinky2", "LeftHandPinky2"}, + {"LPinky3", "LeftHandPinky3"}, + + // 오른쪽 손가락 + {"RThumb1", "RightHandThumb1"}, + {"RThumb2", "RightHandThumb2"}, + {"RThumb3", "RightHandThumb3"}, + {"RIndex1", "RightHandIndex1"}, + {"RIndex2", "RightHandIndex2"}, + {"RIndex3", "RightHandIndex3"}, + {"RMiddle1", "RightHandMiddle1"}, + {"RMiddle2", "RightHandMiddle2"}, + {"RMiddle3", "RightHandMiddle3"}, + {"RRing1", "RightHandRing1"}, + {"RRing2", "RightHandRing2"}, + {"RRing3", "RightHandRing3"}, + {"RPinky1", "RightHandPinky1"}, + {"RPinky2", "RightHandPinky2"}, + {"RPinky3", "RightHandPinky3"}, + }; + void Start() { - - TargetAnimator = GetComponent(); - + BuildTransformCache(); InitializeStreamingClient(); - // StreamingClient 등록 추가 if (StreamingClient != null) { StreamingClient.RegisterSkeleton(this, this.SkeletonAssetName); - //Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'이(가) 등록되었습니다."); } - InitializeBoneMapping(); + // 에디터에서 세팅한 매핑의 Transform 캐시 갱신 + if (boneMappings.Count > 0) + { + RefreshTransformCache(); + } + else + { + Debug.LogWarning("[OptiTrack] 본 매핑이 비어있습니다. Inspector에서 'FBX 분석 → 자동 매핑 생성' 버튼을 눌러주세요.", this); + } - // 주기적으로 스켈레톤 연결 상태를 확인하는 코루틴 시작 StartCoroutine(CheckSkeletonConnectionPeriodically()); } void Update() { - if (TargetAnimator == null) - return; - - // StreamingClient 체크 if (StreamingClient == null) { InitializeStreamingClient(); return; } - // 스켈레톤 이름이 변경되었을 때 + // 스켈레톤 이름 변경 감지 if (previousSkeletonName != SkeletonAssetName) { - // 새 스켈레톤 등록 StreamingClient.RegisterSkeleton(this, SkeletonAssetName); - //Debug.Log($"[OptiTrack] 새 스켈레톤 '{SkeletonAssetName}' 등록"); - - // 스켈레톤 정의 새로 가져오기 m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName); - - if (m_skeletonDef == null) - { - //Debug.LogWarning($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'을(를) 찾을 수 없습니다. Motive에서 올바른 스켈레톤 이름을 확인해주세요.", this); - previousSkeletonName = SkeletonAssetName; // 이름 업데이트 - return; - } - - //Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'을(를) 성공적으로 찾았습니다.", this); - previousSkeletonName = SkeletonAssetName; // 이름 업데이트 + previousSkeletonName = SkeletonAssetName; + if (m_skeletonDef != null) + RebuildBoneIdMapping(); + return; } - // 스켈레톤 정의가 없는 경우 체크 if (m_skeletonDef == null) { m_skeletonDef = StreamingClient.GetSkeletonDefinitionByName(SkeletonAssetName); if (m_skeletonDef == null) - { return; - } + RebuildBoneIdMapping(); } - // 최신 스켈레톤 상태 가져오기 + // 최신 스켈레톤 상태 OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id); - if (skelState == null) - { - //Debug.LogWarning($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}'의 상태가 null입니다. Motive에 마커가 제대로 트래킹되고 있는지 확인해주세요.", this); return; + + // torn read 방지 — 스냅샷 복사 + m_snapshotPositions.Clear(); + m_snapshotOrientations.Clear(); + foreach (var kvp in skelState.BonePoses) + { + m_snapshotPositions[kvp.Key] = kvp.Value.Position; + m_snapshotOrientations[kvp.Key] = kvp.Value.Orientation; } - // 각 본 업데이트 + + // 각 본 업데이트 — Transform 직접 적용 foreach (var bone in m_skeletonDef.Bones) { - string boneName = bone.Name; - string optitrackBoneName = boneName.Contains("_") ? boneName.Substring(boneName.IndexOf('_') + 1) : boneName; + if (!m_boneIdToMappingIndex.TryGetValue(bone.Id, out int mappingIdx)) + continue; - if (m_optitrackToHumanBoneMap.TryGetValue(optitrackBoneName, out HumanBodyBones humanBone)) + var mapping = boneMappings[mappingIdx]; + if (!mapping.isMapped || mapping.cachedTransform == null) + continue; + + if (m_snapshotOrientations.TryGetValue(bone.Id, out Quaternion ori)) { - // 모션 스코프에 따른 본 필터링 - if (!ShouldApplyMotionToBone(optitrackBoneName)) - continue; - - Transform boneTransform = TargetAnimator.GetBoneTransform(humanBone); - if (boneTransform != null) + if (mapping.applyPosition && m_snapshotPositions.TryGetValue(bone.Id, out Vector3 pos)) { - if (skelState.BonePoses.TryGetValue(bone.Id, out OptitrackPose bonePose)) - { - // 손가락의 경우 로컬 포지션 데이터를 받지 않음 - if (!IsFingerBone(optitrackBoneName)) - { - // 위치는 항상 업데이트 (Hip 등 루트 본의 경우) - boneTransform.localPosition = bonePose.Position; - } - - // 회전 업데이트 - boneTransform.localRotation = bonePose.Orientation; - } + mapping.cachedTransform.localPosition = SnapPosition(pos); + } + + if (mapping.applyRotation) + { + mapping.cachedTransform.localRotation = SnapQuaternion(ori); } } } } + /// + /// 하이어라키의 모든 Transform을 이름으로 캐싱 + /// + private void BuildTransformCache() + { + m_allTransforms.Clear(); + var allChildren = GetComponentsInChildren(true); + foreach (var t in allChildren) + { + if (!m_allTransforms.ContainsKey(t.name)) + { + m_allTransforms[t.name] = t; + } + } + } + + /// + /// 기존 매핑의 Transform 캐시만 갱신 + /// + private void RefreshTransformCache() + { + foreach (var mapping in boneMappings) + { + if (!string.IsNullOrEmpty(mapping.fbxNodeName) && m_allTransforms.TryGetValue(mapping.fbxNodeName, out Transform t)) + { + mapping.cachedTransform = t; + mapping.isMapped = true; + } + } + } + + /// + /// OptiTrack 본 ID → boneMappings 인덱스 매핑 구축 + /// + private void RebuildBoneIdMapping() + { + m_boneIdToMappingIndex.Clear(); + debugReceivedBoneNames.Clear(); + if (m_skeletonDef == null) return; + + debugReceivedBoneCount = m_skeletonDef.Bones.Count; + + var nameToIdx = new Dictionary(); + for (int i = 0; i < boneMappings.Count; i++) + { + if (!nameToIdx.ContainsKey(boneMappings[i].optiTrackBoneName)) + nameToIdx[boneMappings[i].optiTrackBoneName] = i; + } + + int matchCount = 0; + foreach (var bone in m_skeletonDef.Bones) + { + string boneName = bone.Name; + string optiName = boneName.Contains("_") ? boneName.Substring(boneName.IndexOf('_') + 1) : boneName; + + // 디버그: Motive에서 수신된 모든 본 기록 + string parentInfo = m_skeletonDef.BoneIdToParentIdMap.TryGetValue(bone.Id, out Int32 parentId) + ? $"parent={parentId}" : "root"; + debugReceivedBoneNames.Add($"[ID:{bone.Id}] {boneName} (suffix: {optiName}, {parentInfo})"); + + if (nameToIdx.TryGetValue(optiName, out int idx)) + { + m_boneIdToMappingIndex[bone.Id] = idx; + matchCount++; + } + else if (logUnmappedBones) + { + Debug.LogWarning($"[OptiTrack] 매핑되지 않은 OptiTrack 본: {boneName} (suffix: {optiName})"); + } + } + + Debug.Log($"[OptiTrack] 본 ID 매핑 완료: {matchCount}/{m_skeletonDef.Bones.Count} 매칭"); + } + private void InitializeStreamingClient() { if (StreamingClient == null) { - // 씬에서 OptitrackStreamingClient 찾기 - StreamingClient = FindObjectOfType(); - if (StreamingClient == null) - { - //Debug.LogWarning("씬에서 OptiTrack Streaming Client를 찾을 수 없습니다. 다음 프레임에서 다시 시도합니다.", this); - } - else - { + StreamingClient = FindAnyObjectByType(); + if (StreamingClient != null) Debug.Log("OptiTrack Streaming Client를 찾았습니다.", this); - } } } - private void InitializeBoneMapping() - { - m_optitrackToHumanBoneMap = new Dictionary(); - - if (TargetAnimator == null || !TargetAnimator.isHuman) - { - Debug.LogError("휴머노이드 아바타가 설정되지 않았습니다.", this); - return; - } - - // OptiTrack 본 이름을 HumanBodyBones enum과 매핑 - // 스켈레톤 에셋 이름을 제외한 기본 매핑 설정 - SetupBoneNameMapping(); - } - private void SetupBoneNameMapping() - { - // 기본 본 매핑 (스켈레톤 에셋 이름 없이) - m_optitrackToHumanBoneMap.Add("Hip", HumanBodyBones.Hips); - m_optitrackToHumanBoneMap.Add("Ab", HumanBodyBones.Spine); - m_optitrackToHumanBoneMap.Add("Chest", HumanBodyBones.Chest); - m_optitrackToHumanBoneMap.Add("Neck", HumanBodyBones.Neck); - m_optitrackToHumanBoneMap.Add("Head", HumanBodyBones.Head); - - // 왼쪽 팔 - m_optitrackToHumanBoneMap.Add("LShoulder", HumanBodyBones.LeftShoulder); - m_optitrackToHumanBoneMap.Add("LUArm", HumanBodyBones.LeftUpperArm); - m_optitrackToHumanBoneMap.Add("LFArm", HumanBodyBones.LeftLowerArm); - m_optitrackToHumanBoneMap.Add("LHand", HumanBodyBones.LeftHand); - - // 오른쪽 팔 - m_optitrackToHumanBoneMap.Add("RShoulder", HumanBodyBones.RightShoulder); - m_optitrackToHumanBoneMap.Add("RUArm", HumanBodyBones.RightUpperArm); - m_optitrackToHumanBoneMap.Add("RFArm", HumanBodyBones.RightLowerArm); - m_optitrackToHumanBoneMap.Add("RHand", HumanBodyBones.RightHand); - - // 왼쪽 다리 - m_optitrackToHumanBoneMap.Add("LThigh", HumanBodyBones.LeftUpperLeg); - m_optitrackToHumanBoneMap.Add("LShin", HumanBodyBones.LeftLowerLeg); - m_optitrackToHumanBoneMap.Add("LFoot", HumanBodyBones.LeftFoot); - m_optitrackToHumanBoneMap.Add("LToe", HumanBodyBones.LeftToes); - - // 오른쪽 다리 - m_optitrackToHumanBoneMap.Add("RThigh", HumanBodyBones.RightUpperLeg); - m_optitrackToHumanBoneMap.Add("RShin", HumanBodyBones.RightLowerLeg); - m_optitrackToHumanBoneMap.Add("RFoot", HumanBodyBones.RightFoot); - m_optitrackToHumanBoneMap.Add("RToe", HumanBodyBones.RightToes); - - // 왼쪽 손가락들 - m_optitrackToHumanBoneMap.Add("LThumb1", HumanBodyBones.LeftThumbProximal); - m_optitrackToHumanBoneMap.Add("LThumb2", HumanBodyBones.LeftThumbIntermediate); - m_optitrackToHumanBoneMap.Add("LThumb3", HumanBodyBones.LeftThumbDistal); - - m_optitrackToHumanBoneMap.Add("LIndex1", HumanBodyBones.LeftIndexProximal); - m_optitrackToHumanBoneMap.Add("LIndex2", HumanBodyBones.LeftIndexIntermediate); - m_optitrackToHumanBoneMap.Add("LIndex3", HumanBodyBones.LeftIndexDistal); - - m_optitrackToHumanBoneMap.Add("LMiddle1", HumanBodyBones.LeftMiddleProximal); - m_optitrackToHumanBoneMap.Add("LMiddle2", HumanBodyBones.LeftMiddleIntermediate); - m_optitrackToHumanBoneMap.Add("LMiddle3", HumanBodyBones.LeftMiddleDistal); - - m_optitrackToHumanBoneMap.Add("LRing1", HumanBodyBones.LeftRingProximal); - m_optitrackToHumanBoneMap.Add("LRing2", HumanBodyBones.LeftRingIntermediate); - m_optitrackToHumanBoneMap.Add("LRing3", HumanBodyBones.LeftRingDistal); - - m_optitrackToHumanBoneMap.Add("LPinky1", HumanBodyBones.LeftLittleProximal); - m_optitrackToHumanBoneMap.Add("LPinky2", HumanBodyBones.LeftLittleIntermediate); - m_optitrackToHumanBoneMap.Add("LPinky3", HumanBodyBones.LeftLittleDistal); - - // 오른쪽 손가락들 - m_optitrackToHumanBoneMap.Add("RThumb1", HumanBodyBones.RightThumbProximal); - m_optitrackToHumanBoneMap.Add("RThumb2", HumanBodyBones.RightThumbIntermediate); - m_optitrackToHumanBoneMap.Add("RThumb3", HumanBodyBones.RightThumbDistal); - - m_optitrackToHumanBoneMap.Add("RIndex1", HumanBodyBones.RightIndexProximal); - m_optitrackToHumanBoneMap.Add("RIndex2", HumanBodyBones.RightIndexIntermediate); - m_optitrackToHumanBoneMap.Add("RIndex3", HumanBodyBones.RightIndexDistal); - - m_optitrackToHumanBoneMap.Add("RMiddle1", HumanBodyBones.RightMiddleProximal); - m_optitrackToHumanBoneMap.Add("RMiddle2", HumanBodyBones.RightMiddleIntermediate); - m_optitrackToHumanBoneMap.Add("RMiddle3", HumanBodyBones.RightMiddleDistal); - - m_optitrackToHumanBoneMap.Add("RRing1", HumanBodyBones.RightRingProximal); - m_optitrackToHumanBoneMap.Add("RRing2", HumanBodyBones.RightRingIntermediate); - m_optitrackToHumanBoneMap.Add("RRing3", HumanBodyBones.RightRingDistal); - - m_optitrackToHumanBoneMap.Add("RPinky1", HumanBodyBones.RightLittleProximal); - m_optitrackToHumanBoneMap.Add("RPinky2", HumanBodyBones.RightLittleIntermediate); - m_optitrackToHumanBoneMap.Add("RPinky3", HumanBodyBones.RightLittleDistal); - } - private IEnumerator CheckSkeletonConnectionPeriodically() { while (true) @@ -257,12 +314,14 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour if (m_skeletonDef != null) { OptitrackSkeletonState skelState = StreamingClient.GetLatestSkeletonState(m_skeletonDef.Id); + bool wasFound = isSkeletonFound; isSkeletonFound = (skelState != null); - if (isSkeletonFound && previousSkeletonName != SkeletonAssetName) + if (isSkeletonFound && !wasFound) { StreamingClient.RegisterSkeleton(this, SkeletonAssetName); previousSkeletonName = SkeletonAssetName; + RebuildBoneIdMapping(); Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}' 연결 성공"); } } @@ -276,38 +335,221 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour } } - private bool IsFingerBone(string boneName) + // 소수점 4자리 이하 제거 (0.0001m = 0.1mm 미만 노이즈 제거) + private const float POS_PRECISION = 10000f; + private static Vector3 SnapPosition(Vector3 v) { - // 손가락 관련 본들 확인 (양손 모두) - return boneName.Contains("Thumb") || - boneName.Contains("Index") || - boneName.Contains("Middle") || - boneName.Contains("Ring") || - boneName.Contains("Pinky"); + return new Vector3( + Mathf.Round(v.x * POS_PRECISION) / POS_PRECISION, + Mathf.Round(v.y * POS_PRECISION) / POS_PRECISION, + Mathf.Round(v.z * POS_PRECISION) / POS_PRECISION + ); } - private bool IsHandBone(string boneName) + // 쿼터니언 소수점 5자리 이하 제거 + 정규화 + private const float ROT_PRECISION = 100000f; + private static Quaternion SnapQuaternion(Quaternion q) { - // 손 관련 본들 확인 (양손 모두) - return boneName.Contains("LHand") || - boneName.Contains("RHand"); + return new Quaternion( + Mathf.Round(q.x * ROT_PRECISION) / ROT_PRECISION, + Mathf.Round(q.y * ROT_PRECISION) / ROT_PRECISION, + Mathf.Round(q.z * ROT_PRECISION) / ROT_PRECISION, + Mathf.Round(q.w * ROT_PRECISION) / ROT_PRECISION + ).normalized; } - private bool ShouldApplyMotionToBone(string boneName) + #region 외부 접근용 헬퍼 + + // HumanBodyBones → OptiTrack 본 이름 매핑 (Humanoid 호환 레이어) + public static readonly Dictionary HumanBoneToOptiName = new Dictionary { - switch (motionScope) + { HumanBodyBones.Hips, "Hip" }, + { HumanBodyBones.Head, "Head" }, + // 스파인/넥은 분배 처리되므로 1:1 매핑은 참고용 + { HumanBodyBones.Spine, "Ab" }, + { HumanBodyBones.Chest, "Chest" }, + { HumanBodyBones.Neck, "Neck" }, + // 왼쪽 팔 + { HumanBodyBones.LeftShoulder, "LShoulder" }, + { HumanBodyBones.LeftUpperArm, "LUArm" }, + { HumanBodyBones.LeftLowerArm, "LFArm" }, + { HumanBodyBones.LeftHand, "LHand" }, + // 오른쪽 팔 + { HumanBodyBones.RightShoulder, "RShoulder" }, + { HumanBodyBones.RightUpperArm, "RUArm" }, + { HumanBodyBones.RightLowerArm, "RFArm" }, + { HumanBodyBones.RightHand, "RHand" }, + // 왼쪽 다리 + { HumanBodyBones.LeftUpperLeg, "LThigh" }, + { HumanBodyBones.LeftLowerLeg, "LShin" }, + { HumanBodyBones.LeftFoot, "LFoot" }, + { HumanBodyBones.LeftToes, "LToe" }, + // 오른쪽 다리 + { HumanBodyBones.RightUpperLeg, "RThigh" }, + { HumanBodyBones.RightLowerLeg, "RShin" }, + { HumanBodyBones.RightFoot, "RFoot" }, + { HumanBodyBones.RightToes, "RToe" }, + // 왼쪽 손가락 + { HumanBodyBones.LeftThumbProximal, "LThumb1" }, + { HumanBodyBones.LeftThumbIntermediate, "LThumb2" }, + { HumanBodyBones.LeftThumbDistal, "LThumb3" }, + { HumanBodyBones.LeftIndexProximal, "LIndex1" }, + { HumanBodyBones.LeftIndexIntermediate, "LIndex2" }, + { HumanBodyBones.LeftIndexDistal, "LIndex3" }, + { HumanBodyBones.LeftMiddleProximal, "LMiddle1" }, + { HumanBodyBones.LeftMiddleIntermediate, "LMiddle2" }, + { HumanBodyBones.LeftMiddleDistal, "LMiddle3" }, + { HumanBodyBones.LeftRingProximal, "LRing1" }, + { HumanBodyBones.LeftRingIntermediate, "LRing2" }, + { HumanBodyBones.LeftRingDistal, "LRing3" }, + { HumanBodyBones.LeftLittleProximal, "LPinky1" }, + { HumanBodyBones.LeftLittleIntermediate, "LPinky2" }, + { HumanBodyBones.LeftLittleDistal, "LPinky3" }, + // 오른쪽 손가락 + { HumanBodyBones.RightThumbProximal, "RThumb1" }, + { HumanBodyBones.RightThumbIntermediate, "RThumb2" }, + { HumanBodyBones.RightThumbDistal, "RThumb3" }, + { HumanBodyBones.RightIndexProximal, "RIndex1" }, + { HumanBodyBones.RightIndexIntermediate, "RIndex2" }, + { HumanBodyBones.RightIndexDistal, "RIndex3" }, + { HumanBodyBones.RightMiddleProximal, "RMiddle1" }, + { HumanBodyBones.RightMiddleIntermediate, "RMiddle2" }, + { HumanBodyBones.RightMiddleDistal, "RMiddle3" }, + { HumanBodyBones.RightRingProximal, "RRing1" }, + { HumanBodyBones.RightRingIntermediate, "RRing2" }, + { HumanBodyBones.RightRingDistal, "RRing3" }, + { HumanBodyBones.RightLittleProximal, "RPinky1" }, + { HumanBodyBones.RightLittleIntermediate, "RPinky2" }, + { HumanBodyBones.RightLittleDistal, "RPinky3" }, + }; + + /// + /// HumanBodyBones enum으로 OptiTrack 매핑된 Transform 반환 + /// (Humanoid 호환 레이어 — sourceAnimator.GetBoneTransform() 대체) + /// + public Transform GetBoneTransform(HumanBodyBones bone) + { + if (HumanBoneToOptiName.TryGetValue(bone, out string optiName)) + return GetMappedTransform(optiName); + return null; + } + + // 스파인 체인 OptiTrack 이름 (Hips 제외, 순서대로) + public static readonly string[] SpineChainOptiNames = { "Ab", "Spine2", "Spine3", "Spine4", "Chest" }; + // 넥 체인 + public static readonly string[] NeckChainOptiNames = { "Neck", "Neck2" }; + + /// + /// OptiTrack 본 이름으로 매핑된 Transform 반환 + /// + public Transform GetMappedTransform(string optiTrackBoneName) + { + foreach (var mapping in boneMappings) { - case MotionApplicationScope.All: - return true; - - case MotionApplicationScope.ExcludeFingersOnly: - return !IsFingerBone(boneName); - - case MotionApplicationScope.ExcludeHandsAndFingers: - return !IsFingerBone(boneName) && !IsHandBone(boneName); - - default: - return true; + if (mapping.optiTrackBoneName == optiTrackBoneName && mapping.isMapped) + return mapping.cachedTransform; + } + return null; + } + + /// + /// 스파인 체인의 모든 Transform을 순서대로 반환 (Ab → Spine2 → Spine3 → Spine4 → Chest) + /// + public List GetSpineChainTransforms() + { + var chain = new List(); + foreach (var name in SpineChainOptiNames) + { + Transform t = GetMappedTransform(name); + if (t != null) + chain.Add(t); + } + return chain; + } + + /// + /// 넥 체인의 모든 Transform을 순서대로 반환 + /// + public List GetNeckChainTransforms() + { + var chain = new List(); + foreach (var name in NeckChainOptiNames) + { + Transform t = GetMappedTransform(name); + if (t != null) + chain.Add(t); + } + return chain; + } + + /// + /// 체인의 누적 로컬 회전을 계산 (각 본의 localRotation을 순서대로 곱함) + /// + public static Quaternion ComputeChainRotation(List chain) + { + Quaternion total = Quaternion.identity; + foreach (var t in chain) + { + total *= t.localRotation; + } + return total; + } + + /// + /// 초기 포즈(T-포즈) 캐싱 — Humanoid가 아니므로 직접 캐싱 + /// + [HideInInspector] public Dictionary restLocalRotations = new Dictionary(); + [HideInInspector] public Dictionary restLocalPositions = new Dictionary(); + [HideInInspector] public bool isRestPoseCached = false; + + /// + /// 현재 포즈를 기준 포즈(T-포즈)로 캐싱 + /// + public void CacheRestPose() + { + restLocalRotations.Clear(); + restLocalPositions.Clear(); + + foreach (var mapping in boneMappings) + { + if (mapping.isMapped && mapping.cachedTransform != null) + { + restLocalRotations[mapping.optiTrackBoneName] = mapping.cachedTransform.localRotation; + restLocalPositions[mapping.optiTrackBoneName] = mapping.cachedTransform.localPosition; + } + } + isRestPoseCached = true; + Debug.Log($"[OptiTrack] 기준 포즈 캐싱 완료: {restLocalRotations.Count}개 본"); + } + + /// + /// 캐싱된 기준 포즈로 복원 (T-포즈 복원) + /// + public void RestoreRestPose() + { + if (!isRestPoseCached) return; + + foreach (var mapping in boneMappings) + { + if (mapping.isMapped && mapping.cachedTransform != null) + { + if (restLocalRotations.TryGetValue(mapping.optiTrackBoneName, out Quaternion rot)) + mapping.cachedTransform.localRotation = rot; + if (restLocalPositions.TryGetValue(mapping.optiTrackBoneName, out Vector3 pos)) + mapping.cachedTransform.localPosition = pos; + } } } + + /// + /// 특정 본의 기준 포즈 로컬 회전 반환 + /// + public Quaternion GetRestLocalRotation(string optiTrackBoneName) + { + if (restLocalRotations.TryGetValue(optiTrackBoneName, out Quaternion rot)) + return rot; + return Quaternion.identity; + } + + #endregion } diff --git a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs index 0744f9d78..69470ce27 100644 --- a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs +++ b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs @@ -9,13 +9,14 @@ namespace KindRetargeting /// 이 스크립트는 원본 아바타(Source)의 포즈 손가락 움직임을 대상 아바타(Target)에 리타게팅(Retargeting)합니다. /// 또한 IK 타겟을 생성하여 대상 아바타의 관절 움직임을 자연스럽게 조정합니다. /// - [DefaultExecutionOrder(1)] + //[DefaultExecutionOrder(-200)] public class CustomRetargetingScript : MonoBehaviour { #region 필드 - [Header("원본 및 대상 아바타 Animator")] - [SerializeField] public Animator sourceAnimator; // 원본 아바타의 Animator + [Header("원본 아바타 (OptiTrack)")] + [SerializeField] public OptitrackSkeletonAnimator_Mingle optitrackSource; + [HideInInspector] public Animator sourceAnimator; // 하위 호환용 (외부 스크립트 참조) [HideInInspector] public Animator targetAnimator; // 대상 아바타의 Animator // IK 컴포넌트 참조 @@ -43,24 +44,37 @@ namespace KindRetargeting [SerializeField] public Vector3 debugAxisNormalizer = Vector3.one; - // HumanPoseHandler를 이용하여 원본 및 대상 아바타의 포즈를 관리 - private HumanPoseHandler sourcePoseHandler; + /// + /// 소스 본 Transform 접근 래퍼 (OptiTrack 매핑 사용) + /// + private Transform GetSourceBoneTransform(HumanBodyBones bone) + { + if (optitrackSource != null) + return optitrackSource.GetBoneTransform(bone); + return null; + } + + // OptiTrack 스파인/넥 분배용 캐시 + private List sourceSpineChain; // 소스 스파인 체인 + private List sourceNeckChain; // 소스 넥 체인 + private List targetSpineBones; // 타겟 스파인 본들 + private List targetNeckBones; // 타겟 넥 본들 + private List spineOffsets; // T-포즈 기준: Inv(가상본 월드회전) * 타겟본 월드회전 + private List neckOffsets; // 넥용 오프셋 + private bool useOptiTrackSpineDistribution = false; + + // HumanPoseHandler를 이용하여 대상 아바타의 포즈를 관리 private HumanPoseHandler targetPoseHandler; - private HumanPose sourcePose; private HumanPose targetPose; // 최적화: 프레임당 한 번만 GetHumanPose 호출하기 위한 플래그 - private bool isSourcePoseCachedThisFrame = false; private bool isTargetPoseCachedThisFrame = false; // 본별 회전 오프셋을 저장하는 딕셔너리 private Dictionary rotationOffsets = new Dictionary(); // HumanBodyBones.LastBone을 이용한 본 순회 범위 - private int lastBoneIndex = 23; - - [Header("손가락 복제 설정")] - [SerializeField] private EnumsList.FingerCopyMode fingerCopyMode = EnumsList.FingerCopyMode.Rotation; + private int lastBoneIndex = 55; // 0~54: 몸체 + 손가락 전부 [Header("무릎 안/밖 조정")] [SerializeField, Range(-1f, 1f)] @@ -156,7 +170,6 @@ namespace KindRetargeting public float footFrontBackOffset; // 발 앞뒤 오프셋 public float footInOutOffset; // 발 안쪽/바깥쪽 오프셋 public float floorHeight; - public EnumsList.FingerCopyMode fingerCopyMode; public List rotationOffsetCache; public float initialHipsHeight; public float avatarScale; @@ -170,40 +183,6 @@ namespace KindRetargeting public float headScale; } - // CopyFingerPoseByMuscle에서 사용할 본 로컬 회전 저장용 (메모리 재사용) - // 위치는 SetHumanPose가 처리하므로 회전만 저장 - private Dictionary savedBoneLocalRotations = - new Dictionary(); - - // 손가락을 제외한 모든 휴먼본 목록 (캐싱) - private static readonly HumanBodyBones[] nonFingerBones = new HumanBodyBones[] - { - HumanBodyBones.Hips, - HumanBodyBones.Spine, - HumanBodyBones.Chest, - HumanBodyBones.UpperChest, - HumanBodyBones.Neck, - HumanBodyBones.Head, - HumanBodyBones.LeftShoulder, - HumanBodyBones.LeftUpperArm, - HumanBodyBones.LeftLowerArm, - HumanBodyBones.LeftHand, - HumanBodyBones.RightShoulder, - HumanBodyBones.RightUpperArm, - HumanBodyBones.RightLowerArm, - HumanBodyBones.RightHand, - HumanBodyBones.LeftUpperLeg, - HumanBodyBones.LeftLowerLeg, - HumanBodyBones.LeftFoot, - HumanBodyBones.LeftToes, - HumanBodyBones.RightUpperLeg, - HumanBodyBones.RightLowerLeg, - HumanBodyBones.RightFoot, - HumanBodyBones.RightToes, - HumanBodyBones.LeftEye, - HumanBodyBones.RightEye, - HumanBodyBones.Jaw - }; // IK 조인트 싱을 위한 구조체 private struct IKJoints @@ -215,56 +194,6 @@ namespace KindRetargeting } private IKJoints sourceIKJoints; - // 손가락 본 인덱스 → 머슬 인덱스 매핑 - // 머슬 순서: 1 Stretched, Spread, 2 Stretched, 3 Stretched (4개씩) - // 소스 아바타 로컬 회전: 엄지는 Y = Spread, 나머지는 X = Spread, 굽힘은 모두 Z축 - private static readonly Dictionary fingerBoneToMuscleIndex = new Dictionary - { - // 왼손 엄지 (24-26) → 머슬 55-58 (55: Stretched1, 56: Spread, 57: Stretched2, 58: Stretched3) - { 24, (55, 56) }, // LeftThumbProximal → Left Thumb 1 Stretched, Spread - { 25, (57, -1) }, // LeftThumbIntermediate → Left Thumb 2 Stretched - { 26, (58, -1) }, // LeftThumbDistal → Left Thumb 3 Stretched - // 왼손 검지 (27-29) → 머슬 59-62 (59: Stretched1, 60: Spread, 61: Stretched2, 62: Stretched3) - { 27, (59, 60) }, // LeftIndexProximal → Left Index 1 Stretched, Spread - { 28, (61, -1) }, // LeftIndexIntermediate → Left Index 2 Stretched (Spread 없음) - { 29, (62, -1) }, // LeftIndexDistal → Left Index 3 Stretched (Spread 없음) - // 왼손 중지 (30-32) → 머슬 63-66 - { 30, (63, 64) }, // LeftMiddleProximal → Left Middle 1 Stretched, Spread - { 31, (65, -1) }, // LeftMiddleIntermediate → Left Middle 2 Stretched - { 32, (66, -1) }, // LeftMiddleDistal → Left Middle 3 Stretched - // 왼손 약지 (33-35) → 머슬 67-70 - { 33, (67, 68) }, // LeftRingProximal → Left Ring 1 Stretched, Spread - { 34, (69, -1) }, // LeftRingIntermediate → Left Ring 2 Stretched - { 35, (70, -1) }, // LeftRingDistal → Left Ring 3 Stretched - // 왼손 소지 (36-38) → 머슬 71-74 - { 36, (71, 72) }, // LeftLittleProximal → Left Little 1 Stretched, Spread - { 37, (73, -1) }, // LeftLittleIntermediate → Left Little 2 Stretched - { 38, (74, -1) }, // LeftLittleDistal → Left Little 3 Stretched - // 오른손 엄지 (39-41) → 머슬 75-78 - { 39, (75, 76) }, // RightThumbProximal → Right Thumb 1 Stretched, Spread - { 40, (77, -1) }, // RightThumbIntermediate → Right Thumb 2 Stretched - { 41, (78, -1) }, // RightThumbDistal → Right Thumb 3 Stretched - // 오른손 검지 (42-44) → 머슬 79-82 - { 42, (79, 80) }, // RightIndexProximal → Right Index 1 Stretched, Spread - { 43, (81, -1) }, // RightIndexIntermediate → Right Index 2 Stretched - { 44, (82, -1) }, // RightIndexDistal → Right Index 3 Stretched - // 오른손 중지 (45-47) → 머슬 83-86 - { 45, (83, 84) }, // RightMiddleProximal → Right Middle 1 Stretched, Spread - { 46, (85, -1) }, // RightMiddleIntermediate → Right Middle 2 Stretched - { 47, (86, -1) }, // RightMiddleDistal → Right Middle 3 Stretched - // 오른손 약지 (48-50) → 머슬 87-90 - { 48, (87, 88) }, // RightRingProximal → Right Ring 1 Stretched, Spread - { 49, (89, -1) }, // RightRingIntermediate → Right Ring 2 Stretched - { 50, (90, -1) }, // RightRingDistal → Right Ring 3 Stretched - // 오른손 소지 (51-53) → 머슬 91-94 - { 51, (91, 92) }, // RightLittleProximal → Right Little 1 Stretched, Spread - { 52, (93, -1) }, // RightLittleIntermediate → Right Little 2 Stretched - { 53, (94, -1) }, // RightLittleDistal → Right Little 3 Stretched - }; - - // 엄지 본 인덱스 (24-26: 왼손, 39-41: 오른손) - Spread 축이 Y임 - private static readonly HashSet thumbBoneIndices = new HashSet { 24, 25, 26, 39, 40, 41 }; - #endregion #region 초기화 @@ -287,7 +216,11 @@ namespace KindRetargeting CalculateAxisNormalizer(); // 원본 및 대상 아바타를 T-포즈로 복원 - SetTPose(sourceAnimator); + // OptiTrack 소스는 Humanoid가 아니므로 현재 포즈를 기준으로 캐싱 + if (optitrackSource != null) + { + optitrackSource.CacheRestPose(); // 현재 포즈(T-포즈)를 기준으로 캐싱 + } SetTPose(targetAnimator); // T-포즈에서의 머리 정면 방향 캐싱 (캘리브레이션용) @@ -333,6 +266,9 @@ namespace KindRetargeting } } + // OptiTrack 스파인 분배 초기화 + InitializeOptiTrackSpineDistribution(); + // 어깨 보정 모듈 초기화 if (targetAnimator != null) shoulderCorrection.Initialize(targetAnimator); @@ -456,29 +392,12 @@ namespace KindRetargeting /// private void InitializeHumanPoseHandlers() { - if (sourceAnimator != null && sourceAnimator.avatar != null) - { - sourcePoseHandler = new HumanPoseHandler(sourceAnimator.avatar, sourceAnimator.transform); - } - if (targetAnimator != null && targetAnimator.avatar != null) { targetPoseHandler = new HumanPoseHandler(targetAnimator.avatar, targetAnimator.transform); } } - /// - /// 소스 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출. - /// - private void EnsureSourcePoseCached() - { - if (!isSourcePoseCachedThisFrame && sourcePoseHandler != null) - { - sourcePoseHandler.GetHumanPose(ref sourcePose); - isSourcePoseCachedThisFrame = true; - } - } - /// /// 타겟 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출. /// @@ -496,9 +415,9 @@ namespace KindRetargeting /// private void CalculateRotationOffsets(bool isIPose = false) { - if (sourceAnimator == null || targetAnimator == null) + if (optitrackSource == null || targetAnimator == null) { - Debug.LogError("소스 또는 타겟 Animator가 설정되지 않았습니다."); + Debug.LogError("소스 OptiTrack 또는 타겟 Animator가 설정되지 않았습니다."); return; } @@ -512,7 +431,7 @@ namespace KindRetargeting for (int i = 0; i <= (isIPose ? 23 : 54); i++) { HumanBodyBones bone = (HumanBodyBones)i; - Transform sourceBone = sourceAnimator.GetBoneTransform(bone); + Transform sourceBone = GetSourceBoneTransform(bone); Transform targetBone = targetAnimator.GetBoneTransform(bone); if (sourceBone != null && targetBone != null) @@ -559,7 +478,6 @@ namespace KindRetargeting footFrontBackOffset = footFrontBackOffset, footInOutOffset = footInOutOffset, floorHeight = floorHeight, - fingerCopyMode = fingerCopyMode, rotationOffsetCache = offsetCache, initialHipsHeight = initialHipsHeight, avatarScale = avatarScale, @@ -618,7 +536,6 @@ namespace KindRetargeting footFrontBackOffset = settings.footFrontBackOffset; footInOutOffset = settings.footInOutOffset; floorHeight = settings.floorHeight; - fingerCopyMode = settings.fingerCopyMode; initialHipsHeight = settings.initialHipsHeight; avatarScale = settings.avatarScale; previousScale = avatarScale; @@ -765,14 +682,14 @@ namespace KindRetargeting /// private void CalculateFingerRotationOffsets() { - if (sourceAnimator == null || targetAnimator == null) + if (optitrackSource == null || targetAnimator == null) return; // 손가락 본들 (24~54)의 오프셋 계산 for (int i = 24; i <= 54; i++) { HumanBodyBones bone = (HumanBodyBones)i; - Transform sourceBone = sourceAnimator.GetBoneTransform(bone); + Transform sourceBone = GetSourceBoneTransform(bone); Transform targetBone = targetAnimator.GetBoneTransform(bone); if (sourceBone != null && targetBone != null) @@ -794,7 +711,7 @@ namespace KindRetargeting private void InitializeIKJoints() { // IK 루트 찾기 - Transform sourceIKRoot = sourceAnimator.transform.Find("IK"); + Transform sourceIKRoot = optitrackSource.transform.Find("IK"); if (sourceIKRoot == null) { Debug.LogError("소스 아바타에서 IK 루트를 찾을 수 없습니다."); @@ -821,43 +738,20 @@ namespace KindRetargeting /// void Update() { - // 최적화: 프레임 시작 시 포즈 캐시 플래그 리셋 - isSourcePoseCachedThisFrame = false; isTargetPoseCachedThisFrame = false; - // 포즈 복사 및 동기화 CopyPoseToTarget(); UpdateIKTargets(); - // IK 중간 타겟 업데이트 - // 손가락 포즈 동기화 - switch (fingerCopyMode) - { - case EnumsList.FingerCopyMode.MuscleData: - CopyFingerPoseByMuscle(); - break; - case EnumsList.FingerCopyMode.Rotation: - CopyFingerPoseByRotation(); - break; - } + // 손가락은 SyncBoneRotations에서 함께 처리됨 (lastBoneIndex=55) - // 손가락 셰이핑 (기존 ExecutionOrder 2) fingerShaped.OnUpdate(); - - // 어깨 보정 (기존 ExecutionOrder 3) shoulderCorrection.OnUpdate(); - - // 사지 가중치 (기존 ExecutionOrder 4) limbWeight.OnUpdate(); - - // 발 접지 Pre-IK (기존 ExecutionOrder 5) footGrounding.OnUpdate(); - - // IK 솔버 (기존 ExecutionOrder 6) ikSolver.OnUpdate(); - // 스케일 변경 확인 및 적용 if (!Mathf.Approximately(previousScale, avatarScale)) { ApplyScale(); @@ -870,9 +764,7 @@ namespace KindRetargeting /// void LateUpdate() { - // 발 접지 Post-IK (기존 FootGroundingController LateUpdate) footGrounding.OnLateUpdate(); - ApplyHeadRotationOffset(); ApplyHeadScale(); } @@ -1008,50 +900,6 @@ namespace KindRetargeting Debug.Log($"[{gameObject.name}] 정면 캘리브레이션 완료 - Offset X: {euler.x:F1}°, Y: {euler.y:F1}°, Z: {euler.z:F1}°"); } - /// - /// 머슬 데이터를 사용하여 손가락 포즈를 복제합니다. - /// SetHumanPose가 모든 본에 영향을 미치므로, 손가락을 제외한 모든 본의 Transform을 저장하고 복원합니다. - /// - private void CopyFingerPoseByMuscle() - { - if (sourcePoseHandler == null || targetPoseHandler == null) - return; - - // 1. 손가락을 제외한 모든 본의 로컬 회전 저장 (위치는 복원 불필요) - savedBoneLocalRotations.Clear(); - for (int i = 0; i < nonFingerBones.Length; i++) - { - Transform bone = targetAnimator.GetBoneTransform(nonFingerBones[i]); - if (bone != null) - { - savedBoneLocalRotations[nonFingerBones[i]] = bone.localRotation; - } - } - - // 2. 머슬 데이터 업데이트 (최적화: 캐싱된 포즈 사용) - EnsureSourcePoseCached(); - EnsureTargetPoseCached(); - - for (int i = 0; i < 40; i++) - { - int muscleIndex = 55 + i; - targetPose.muscles[muscleIndex] = sourcePose.muscles[muscleIndex]; - } - - // 3. 머슬 포즈 적용 (손가락 포함 전체 본에 영향) - targetPoseHandler.SetHumanPose(ref targetPose); - - // 4. 손가락을 제외한 모든 본의 로컬 회전 복원 (위치는 복원하지 않음 - 본 길이 변형 방지) - foreach (var kvp in savedBoneLocalRotations) - { - Transform bone = targetAnimator.GetBoneTransform(kvp.Key); - if (bone != null) - { - bone.localRotation = kvp.Value; - } - } - } - /// /// 회전값을 사용하여 손가락 포즈를 복제합니다. /// @@ -1060,7 +908,7 @@ namespace KindRetargeting for (int i = 24; i <= 53; i++) { HumanBodyBones bone = (HumanBodyBones)i; - Transform sourceBone = sourceAnimator.GetBoneTransform(bone); + Transform sourceBone = GetSourceBoneTransform(bone); Transform targetBone = targetAnimator.GetBoneTransform(bone); if (sourceBone != null && targetBone != null) @@ -1091,7 +939,7 @@ namespace KindRetargeting private void CopyPoseToTarget() { // 힙(루트 본) 동기화 - Transform sourceHips = sourceAnimator.GetBoneTransform(HumanBodyBones.Hips); + Transform sourceHips = GetSourceBoneTransform(HumanBodyBones.Hips); Transform targetHips = targetAnimator.GetBoneTransform(HumanBodyBones.Hips); if (sourceHips != null && targetHips != null) @@ -1141,6 +989,8 @@ namespace KindRetargeting // 힙을 제외한 본들의 회전 동기화 SyncBoneRotations(skipBone: HumanBodyBones.Hips); + + ApplyOptiTrackSpineNeckDistribution(); } /// @@ -1149,7 +999,6 @@ namespace KindRetargeting /// 동기화에서 제외할 본 private void SyncBoneRotations(HumanBodyBones skipBone) { - // 기본 몸체 본만 동기화 (손가락 제외) for (int i = 0; i < lastBoneIndex; i++) { HumanBodyBones bone = (HumanBodyBones)i; @@ -1157,7 +1006,10 @@ namespace KindRetargeting if (bone == skipBone) continue; - Transform sourceBone = sourceAnimator.GetBoneTransform(bone); + if (useOptiTrackSpineDistribution && IsOptiTrackDistributedBone(bone)) + continue; + + Transform sourceBone = GetSourceBoneTransform(bone); Transform targetBone = targetAnimator.GetBoneTransform(bone); if (sourceBone != null && targetBone != null) @@ -1179,6 +1031,134 @@ namespace KindRetargeting #endregion + #region OptiTrack 스파인/넥 분배 + + /// + /// OptiTrack 소스 감지 및 스파인/넥 분배 초기화 + /// + private void InitializeOptiTrackSpineDistribution() + { + if (optitrackSource == null || targetAnimator == null) + { + useOptiTrackSpineDistribution = false; + return; + } + + // 소스 스파인/넥 체인 가져오기 + sourceSpineChain = optitrackSource.GetSpineChainTransforms(); + sourceNeckChain = optitrackSource.GetNeckChainTransforms(); + + // 타겟 Humanoid 스파인 본들 수집 (존재하는 본만) + targetSpineBones = new List(); + HumanBodyBones[] spineEnums = { HumanBodyBones.Spine, HumanBodyBones.Chest, HumanBodyBones.UpperChest }; + foreach (var bone in spineEnums) + { + Transform t = targetAnimator.GetBoneTransform(bone); + if (t != null) + targetSpineBones.Add(t); + } + + // 타겟 Humanoid 넥 본 수집 + targetNeckBones = new List(); + HumanBodyBones[] neckEnums = { HumanBodyBones.Neck }; + foreach (var bone in neckEnums) + { + Transform t = targetAnimator.GetBoneTransform(bone); + if (t != null) + targetNeckBones.Add(t); + } + + useOptiTrackSpineDistribution = sourceSpineChain.Count > 0 && targetSpineBones.Count > 0; + + if (!useOptiTrackSpineDistribution) return; + + // ── T-포즈 기준으로 가상 본 오프셋 계산 (OffsetTransfer 패턴) ── + // 소스와 타겟 모두 T-포즈 상태에서 호출되어야 함 + spineOffsets = CalculateVirtualBoneOffsets(sourceSpineChain, targetSpineBones); + neckOffsets = CalculateVirtualBoneOffsets(sourceNeckChain, targetNeckBones); + + Debug.Log($"[Retargeting] OptiTrack 스파인 분배 활성화: " + + $"소스 스파인 {sourceSpineChain.Count}본 → 타겟 {targetSpineBones.Count}본, " + + $"소스 넥 {sourceNeckChain.Count}본 → 타겟 {targetNeckBones.Count}본"); + } + + /// + /// T-포즈 기준으로 가상 본의 월드 회전과 타겟 본의 월드 회전 사이 오프셋을 계산. + /// offset = Inv(가상본 월드회전) * 타겟본 월드회전 + /// + private List CalculateVirtualBoneOffsets(List sourceChain, List targetBones) + { + var offsets = new List(); + int srcCount = sourceChain.Count; + int tgtCount = targetBones.Count; + + for (int t = 0; t < tgtCount; t++) + { + // 이 타겟 본이 담당할 소스 범위의 마지막 본의 월드 회전을 가상 본 회전으로 사용 + // 예) 5본→2본: 가상본0 = sourceChain[2], 가상본1 = sourceChain[4] + int lastSrcIdx = Mathf.Min(Mathf.RoundToInt((float)(t + 1) / tgtCount * srcCount) - 1, srcCount - 1); + Quaternion virtualBoneWorldRot = sourceChain[lastSrcIdx].rotation; + Quaternion targetWorldRot = targetBones[t].rotation; + + // OffsetTransfer 패턴: offset = Inv(source) * target + offsets.Add(Quaternion.Inverse(virtualBoneWorldRot) * targetWorldRot); + } + + return offsets; + } + + /// + /// OptiTrack 분배 대상 본인지 확인 (스파인 + 넥) + /// + private bool IsOptiTrackDistributedBone(HumanBodyBones bone) + { + return bone == HumanBodyBones.Spine || + bone == HumanBodyBones.Chest || + bone == HumanBodyBones.UpperChest || + bone == HumanBodyBones.Neck; + } + + /// + /// 소스 스파인/넥 체인을 가상 본으로 그룹핑하여 월드 회전 + 오프셋으로 타겟에 적용 + /// + private void ApplyOptiTrackSpineNeckDistribution() + { + if (!useOptiTrackSpineDistribution) return; + + // 스파인 분배 + if (sourceSpineChain.Count > 0 && targetSpineBones.Count > 0 && spineOffsets != null) + { + ApplyVirtualBoneRotations(sourceSpineChain, targetSpineBones, spineOffsets); + } + + // 넥 분배 + if (sourceNeckChain.Count > 0 && targetNeckBones.Count > 0 && neckOffsets != null) + { + ApplyVirtualBoneRotations(sourceNeckChain, targetNeckBones, neckOffsets); + } + } + + /// + /// 소스 체인을 가상 본으로 그룹핑 → 가상 본 월드 회전 계산 → 오프셋 적용하여 타겟에 세팅 + /// + private void ApplyVirtualBoneRotations(List sourceChain, List targetBones, List offsets) + { + int srcCount = sourceChain.Count; + int tgtCount = targetBones.Count; + + for (int t = 0; t < tgtCount; t++) + { + // 가상 본 = 이 타겟이 담당하는 소스 그룹의 마지막 본 월드 회전 + int lastSrcIdx = Mathf.Min(Mathf.RoundToInt((float)(t + 1) / tgtCount * srcCount) - 1, srcCount - 1); + Quaternion virtualBoneWorldRot = sourceChain[lastSrcIdx].rotation; + + // OffsetTransfer 패턴: 타겟.rotation = 가상본월드회전 * offset + targetBones[t].rotation = virtualBoneWorldRot * offsets[t]; + } + } + + #endregion + #region IK 타겟 생성 및 관관리 /// @@ -1272,7 +1252,7 @@ namespace KindRetargeting { if (endTarget == null) return; - Transform sourceBone = sourceAnimator.GetBoneTransform(endBone); + Transform sourceBone = GetSourceBoneTransform(endBone); Transform targetBone = targetAnimator.GetBoneTransform(endBone); if (sourceBone != null && targetBone != null) @@ -1369,16 +1349,6 @@ namespace KindRetargeting { var pose = humanPoseClip.GetPose(); HumanPoseTransfer.SetPose(avatar, transform, pose); - - // 소스 아바타의 UpperChest 본 로컬 포지션 초기화 - if (animator == sourceAnimator) - { - Transform upperChest = animator.GetBoneTransform(HumanBodyBones.UpperChest); - if (upperChest != null) - { - upperChest.localPosition = Vector3.zero; - } - } } else { @@ -1416,7 +1386,6 @@ namespace KindRetargeting /// void OnDestroy() { - sourcePoseHandler?.Dispose(); targetPoseHandler?.Dispose(); fingerShaped.Cleanup(); } @@ -1477,18 +1446,12 @@ namespace KindRetargeting return; } - // T-포즈로 복원 - SetTPose(sourceAnimator); + // 포즈 복원 + if (optitrackSource != null) + optitrackSource.RestoreRestPose(); SetTPose(targetAnimator); - // 소스 아바타의 UpperChest 본 로컬 포지션 초기화 - Transform upperChest = sourceAnimator.GetBoneTransform(HumanBodyBones.UpperChest); - if (upperChest != null) - { - upperChest.localPosition = Vector3.zero; - } - // HumanPoseHandler 초기화 InitializeHumanPoseHandlers(); @@ -1505,15 +1468,15 @@ namespace KindRetargeting originalScales.Clear(); originalPositions.Clear(); - Transform parentTransform = sourceAnimator.transform.parent; + Transform parentTransform = optitrackSource.transform.parent; if (parentTransform != null) { for (int i = 0; i < parentTransform.childCount; i++) { Transform child = parentTransform.GetChild(i); - // sourceAnimator를 제외한 모든 자식 오브젝트 추가 - if (child != sourceAnimator.transform) + // optitrackSource를 제외한 모든 자식 오브젝트 추가 + if (child != optitrackSource.transform) { scalableObjects.Add(child); // 초기 스케일과 위치 저장 diff --git a/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs b/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs index 3bf161e96..41227cd6c 100644 --- a/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs +++ b/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs @@ -23,7 +23,7 @@ namespace KindRetargeting if (commonUss != null) root.styleSheets.Add(commonUss); // ── 기본 설정 ── - root.Add(new PropertyField(serializedObject.FindProperty("sourceAnimator"), "원본 Animator")); + root.Add(new PropertyField(serializedObject.FindProperty("optitrackSource"), "원본 OptiTrack")); // ── 아바타 크기 ── var scaleFoldout = new Foldout { text = "아바타 크기 설정", value = true }; @@ -476,11 +476,11 @@ namespace KindRetargeting private void AutoCalibrateAll(CustomRetargetingScript script, SerializedObject so) { - Animator source = script.sourceAnimator; + var source = script.optitrackSource; Animator targetAnim = script.targetAnimator; - if (source == null || targetAnim == null || !source.isHuman || !targetAnim.isHuman) + if (source == null || targetAnim == null || !targetAnim.isHuman) { - Debug.LogWarning("소스/타겟 Animator가 없거나 Humanoid가 아닙니다."); + Debug.LogWarning("소스 OptiTrack 또는 타겟 Animator가 설정되지 않았습니다."); return; } @@ -526,11 +526,11 @@ namespace KindRetargeting private float CalculateHipsOffsetFromLegDifference(CustomRetargetingScript script) { - Animator source = script.sourceAnimator; + var source = script.optitrackSource; Animator targetAnim = script.targetAnimator; if (source == null || targetAnim == null) return 0f; - float sourceLeg = GetLegLength(source); + float sourceLeg = GetSourceLegLength(source); float targetLeg = GetLegLength(targetAnim); if (sourceLeg < 0.01f || targetLeg < 0.01f) return 0f; @@ -546,6 +546,15 @@ namespace KindRetargeting return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); } + private float GetSourceLegLength(OptitrackSkeletonAnimator_Mingle source) + { + Transform upper = source.GetBoneTransform(HumanBodyBones.LeftUpperLeg); + Transform lower = source.GetBoneTransform(HumanBodyBones.LeftLowerLeg); + Transform foot = source.GetBoneTransform(HumanBodyBones.LeftFoot); + if (upper == null || lower == null || foot == null) return 0f; + return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); + } + private void CalibrateHeadToForward(SerializedObject so, SerializedProperty xProp, SerializedProperty yProp, SerializedProperty zProp) { CustomRetargetingScript script = so.targetObject as CustomRetargetingScript; diff --git a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs index 13d306daf..644e23988 100644 --- a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs +++ b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs @@ -906,12 +906,12 @@ public class RetargetingControlWindow : EditorWindow /// private void AutoCalibrateAll(CustomRetargetingScript script, SerializedObject so) { - Animator source = script.sourceAnimator; + var source = script.optitrackSource; Animator target = script.targetAnimator; - if (source == null || target == null || !source.isHuman || !target.isHuman) + if (source == null || target == null || !target.isHuman) { - Debug.LogWarning("소스/타겟 Animator가 없거나 Humanoid가 아닙니다."); + Debug.LogWarning("소스 OptiTrack 또는 타겟 Animator가 설정되지 않았습니다."); return; } @@ -978,16 +978,16 @@ public class RetargetingControlWindow : EditorWindow /// private float CalculateHipsOffsetFromLegDifference(CustomRetargetingScript script) { - Animator source = script.sourceAnimator; + var source = script.optitrackSource; Animator target = script.targetAnimator; - if (source == null || target == null || !source.isHuman || !target.isHuman) + if (source == null || target == null || !target.isHuman) { - Debug.LogWarning("소스/타겟 Animator가 없거나 Humanoid가 아닙니다."); + Debug.LogWarning("소스 OptiTrack 또는 타겟 Animator가 설정되지 않았습니다."); return 0f; } - float sourceLeg = GetLegLength(source); + float sourceLeg = GetSourceLegLength(source); float targetLeg = GetLegLength(target); if (sourceLeg < 0.01f || targetLeg < 0.01f) @@ -1015,6 +1015,18 @@ public class RetargetingControlWindow : EditorWindow + Vector3.Distance(lowerLeg.position, foot.position); } + private float GetSourceLegLength(OptitrackSkeletonAnimator_Mingle source) + { + Transform upperLeg = source.GetBoneTransform(HumanBodyBones.LeftUpperLeg); + Transform lowerLeg = source.GetBoneTransform(HumanBodyBones.LeftLowerLeg); + Transform foot = source.GetBoneTransform(HumanBodyBones.LeftFoot); + + if (upperLeg == null || lowerLeg == null || foot == null) return 0f; + + return Vector3.Distance(upperLeg.position, lowerLeg.position) + + Vector3.Distance(lowerLeg.position, foot.position); + } + private void CalibrateHeadToForward(SerializedObject so, SerializedProperty xProp, SerializedProperty yProp, SerializedProperty zProp) { CustomRetargetingScript script = so.targetObject as CustomRetargetingScript; diff --git a/Assets/Scripts/KindRetargeting/LimbWeightController.cs b/Assets/Scripts/KindRetargeting/LimbWeightController.cs index 10970dfb2..6d434913f 100644 --- a/Assets/Scripts/KindRetargeting/LimbWeightController.cs +++ b/Assets/Scripts/KindRetargeting/LimbWeightController.cs @@ -157,8 +157,8 @@ namespace KindRetargeting if (otherCrs == crs) continue; // 왼손과 오른손 Transform 가져오기 - Transform leftHand = otherCrs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); - Transform rightHand = otherCrs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.RightHand); + Transform leftHand = otherCrs?.optitrackSource?.GetBoneTransform(HumanBodyBones.LeftHand); + Transform rightHand = otherCrs?.optitrackSource?.GetBoneTransform(HumanBodyBones.RightHand); // 손이 존재하면 props 리스트에 추가 if (leftHand != null) @@ -192,8 +192,8 @@ namespace KindRetargeting // 왼쪽 팔 가중치 업데이트 if (ikSolver.leftArm.target != null && ikSolver.rightArm.target != null) { - Transform leftHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); - Transform rightHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.RightHand); + Transform leftHandTransform = crs?.optitrackSource?.GetBoneTransform(HumanBodyBones.LeftHand); + Transform rightHandTransform = crs?.optitrackSource?.GetBoneTransform(HumanBodyBones.RightHand); if (leftHandTransform != null && rightHandTransform != null && props != null && props.Count > 0) { @@ -215,7 +215,7 @@ namespace KindRetargeting { if (ikSolver == null || crs == null || characterRoot == null) return; - Transform hips = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.Hips); + Transform hips = crs?.optitrackSource?.GetBoneTransform(HumanBodyBones.Hips); if (hips == null) return; // 캐릭터 루트 기준으로 히프의 로컬 높이 계산 @@ -264,8 +264,8 @@ namespace KindRetargeting if (ikSolver == null || crs == null) return; // 프랍과의 거리에 따른 웨이트 업데이트 - Transform leftHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); - Transform rightHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.RightHand); + Transform leftHandTransform = crs?.optitrackSource?.GetBoneTransform(HumanBodyBones.LeftHand); + Transform rightHandTransform = crs?.optitrackSource?.GetBoneTransform(HumanBodyBones.RightHand); if (leftHandTransform != null && rightHandTransform != null && props != null) { @@ -298,7 +298,7 @@ namespace KindRetargeting { if (crs == null) return; - Transform hipsTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.Hips); + Transform hipsTransform = crs.optitrackSource.GetBoneTransform(HumanBodyBones.Hips); if (hipsTransform != null && props != null) { float minDistance = float.MaxValue; @@ -473,7 +473,7 @@ namespace KindRetargeting { if (crs == null || ikSolver == null) return; - Transform hipsTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.Hips); + Transform hipsTransform = crs.optitrackSource.GetBoneTransform(HumanBodyBones.Hips); if (hipsTransform != null) { float groundHeight = characterRoot.position.y; diff --git a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs index ea74f944e..cae5f77e6 100644 --- a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs +++ b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs @@ -647,11 +647,11 @@ namespace KindRetargeting.Remote var script = FindCharacter(characterId); if (script == null) return; - Animator source = script.sourceAnimator; + var source = script.optitrackSource; Animator targetAnim = script.targetAnimator; - if (source == null || targetAnim == null || !source.isHuman || !targetAnim.isHuman) + if (source == null || targetAnim == null || !targetAnim.isHuman) { - SendStatus(false, "소스/타겟 Animator가 없거나 Humanoid가 아닙니다."); + SendStatus(false, "소스 OptiTrack 또는 타겟 Animator가 설정되지 않았습니다."); return; } @@ -668,7 +668,7 @@ namespace KindRetargeting.Remote { yield return null; // 1프레임 대기 - Animator source = script.sourceAnimator; + var source = script.optitrackSource; Animator targetAnim = script.targetAnimator; Transform sourceNeck = source.GetBoneTransform(HumanBodyBones.Neck); @@ -696,17 +696,26 @@ namespace KindRetargeting.Remote private float CalculateHipsOffsetFromLegDifference(CustomRetargetingScript script) { - Animator source = script.sourceAnimator; + var source = script.optitrackSource; Animator targetAnim = script.targetAnimator; if (source == null || targetAnim == null) return 0f; - float sourceLeg = GetLegLength(source); + float sourceLeg = GetSourceLegLength(source); float targetLeg = GetLegLength(targetAnim); if (sourceLeg < 0.01f || targetLeg < 0.01f) return 0f; return targetLeg - sourceLeg; } + private float GetSourceLegLength(OptitrackSkeletonAnimator_Mingle source) + { + Transform upper = source.GetBoneTransform(HumanBodyBones.LeftUpperLeg); + Transform lower = source.GetBoneTransform(HumanBodyBones.LeftLowerLeg); + Transform foot = source.GetBoneTransform(HumanBodyBones.LeftFoot); + if (upper == null || lower == null || foot == null) return 0f; + return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); + } + private float GetLegLength(Animator animator) { Transform upper = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg);