Fix : 리타겟팅 웨이브 특성 강화
This commit is contained in:
parent
3f3741719c
commit
f00567b48e
8
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone.meta
vendored
Normal file
8
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone.meta
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11bcd309fb41db841bca0a02919c3778
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx
(Stored with Git LFS)
vendored
Normal file
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.fbx
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
@ -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:
|
||||
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab
(Stored with Git LFS)
vendored
Normal file
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1469350b1e555d45a488d3dd26f3f0c
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
@ -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<Transform>(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);
|
||||
}
|
||||
|
||||
private void AutoGenerateMappingsEditor(OptitrackSkeletonAnimator_Mingle script)
|
||||
{
|
||||
script.boneMappings.Clear();
|
||||
|
||||
var allTransforms = script.GetComponentsInChildren<Transform>(true);
|
||||
var transformMap = new Dictionary<string, Transform>();
|
||||
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} 본 매핑 성공");
|
||||
}
|
||||
}
|
||||
@ -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<OptiTrackBoneMapping> boneMappings = new List<OptiTrackBoneMapping>();
|
||||
|
||||
[Header("디버그")]
|
||||
public bool logUnmappedBones = false;
|
||||
|
||||
private OptitrackSkeletonDefinition m_skeletonDef;
|
||||
private Dictionary<string, HumanBodyBones> m_optitrackToHumanBoneMap;
|
||||
|
||||
private string previousSkeletonName;
|
||||
|
||||
[HideInInspector]
|
||||
public bool isSkeletonFound = false;
|
||||
|
||||
// 에디터 디버그용 — 런타임에 Motive에서 실제로 수신된 본 목록
|
||||
[HideInInspector]
|
||||
public List<string> debugReceivedBoneNames = new List<string>();
|
||||
[HideInInspector]
|
||||
public int debugReceivedBoneCount = 0;
|
||||
|
||||
private float updateInterval = 0.1f;
|
||||
|
||||
// 본 ID → 매핑 인덱스 (빠른 룩업)
|
||||
private Dictionary<Int32, int> m_boneIdToMappingIndex = new Dictionary<Int32, int>();
|
||||
// 본 이름 → Transform 캐시 (전체 하이어라키)
|
||||
private Dictionary<string, Transform> m_allTransforms = new Dictionary<string, Transform>();
|
||||
|
||||
// torn read 방지용 스냅샷 버퍼
|
||||
private Dictionary<Int32, Vector3> m_snapshotPositions = new Dictionary<Int32, Vector3>();
|
||||
private Dictionary<Int32, Quaternion> m_snapshotOrientations = new Dictionary<Int32, Quaternion>();
|
||||
|
||||
// OptiTrack 본 이름 → FBX 노드 접미사 기본 매핑
|
||||
public static readonly Dictionary<string, string> DefaultOptiToFbxSuffix = new Dictionary<string, string>
|
||||
{
|
||||
// 몸통 (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<Animator>();
|
||||
|
||||
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;
|
||||
}
|
||||
mapping.cachedTransform.localPosition = SnapPosition(pos);
|
||||
}
|
||||
|
||||
// 회전 업데이트
|
||||
boneTransform.localRotation = bonePose.Orientation;
|
||||
}
|
||||
if (mapping.applyRotation)
|
||||
{
|
||||
mapping.cachedTransform.localRotation = SnapQuaternion(ori);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 하이어라키의 모든 Transform을 이름으로 캐싱
|
||||
/// </summary>
|
||||
private void BuildTransformCache()
|
||||
{
|
||||
m_allTransforms.Clear();
|
||||
var allChildren = GetComponentsInChildren<Transform>(true);
|
||||
foreach (var t in allChildren)
|
||||
{
|
||||
if (!m_allTransforms.ContainsKey(t.name))
|
||||
{
|
||||
m_allTransforms[t.name] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 매핑의 Transform 캐시만 갱신
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 본 ID → boneMappings 인덱스 매핑 구축
|
||||
/// </summary>
|
||||
private void RebuildBoneIdMapping()
|
||||
{
|
||||
m_boneIdToMappingIndex.Clear();
|
||||
debugReceivedBoneNames.Clear();
|
||||
if (m_skeletonDef == null) return;
|
||||
|
||||
debugReceivedBoneCount = m_skeletonDef.Bones.Count;
|
||||
|
||||
var nameToIdx = new Dictionary<string, int>();
|
||||
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<OptitrackStreamingClient>();
|
||||
if (StreamingClient == null)
|
||||
{
|
||||
//Debug.LogWarning("씬에서 OptiTrack Streaming Client를 찾을 수 없습니다. 다음 프레임에서 다시 시도합니다.", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamingClient = FindAnyObjectByType<OptitrackStreamingClient>();
|
||||
if (StreamingClient != null)
|
||||
Debug.Log("OptiTrack Streaming Client를 찾았습니다.", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeBoneMapping()
|
||||
{
|
||||
m_optitrackToHumanBoneMap = new Dictionary<string, HumanBodyBones>();
|
||||
|
||||
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<HumanBodyBones, string> HumanBoneToOptiName = new Dictionary<HumanBodyBones, string>
|
||||
{
|
||||
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" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// HumanBodyBones enum으로 OptiTrack 매핑된 Transform 반환
|
||||
/// (Humanoid 호환 레이어 — sourceAnimator.GetBoneTransform() 대체)
|
||||
/// </summary>
|
||||
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" };
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 본 이름으로 매핑된 Transform 반환
|
||||
/// </summary>
|
||||
public Transform GetMappedTransform(string optiTrackBoneName)
|
||||
{
|
||||
foreach (var mapping in boneMappings)
|
||||
{
|
||||
case MotionApplicationScope.All:
|
||||
return true;
|
||||
if (mapping.optiTrackBoneName == optiTrackBoneName && mapping.isMapped)
|
||||
return mapping.cachedTransform;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
case MotionApplicationScope.ExcludeFingersOnly:
|
||||
return !IsFingerBone(boneName);
|
||||
/// <summary>
|
||||
/// 스파인 체인의 모든 Transform을 순서대로 반환 (Ab → Spine2 → Spine3 → Spine4 → Chest)
|
||||
/// </summary>
|
||||
public List<Transform> GetSpineChainTransforms()
|
||||
{
|
||||
var chain = new List<Transform>();
|
||||
foreach (var name in SpineChainOptiNames)
|
||||
{
|
||||
Transform t = GetMappedTransform(name);
|
||||
if (t != null)
|
||||
chain.Add(t);
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
case MotionApplicationScope.ExcludeHandsAndFingers:
|
||||
return !IsFingerBone(boneName) && !IsHandBone(boneName);
|
||||
/// <summary>
|
||||
/// 넥 체인의 모든 Transform을 순서대로 반환
|
||||
/// </summary>
|
||||
public List<Transform> GetNeckChainTransforms()
|
||||
{
|
||||
var chain = new List<Transform>();
|
||||
foreach (var name in NeckChainOptiNames)
|
||||
{
|
||||
Transform t = GetMappedTransform(name);
|
||||
if (t != null)
|
||||
chain.Add(t);
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
default:
|
||||
return true;
|
||||
/// <summary>
|
||||
/// 체인의 누적 로컬 회전을 계산 (각 본의 localRotation을 순서대로 곱함)
|
||||
/// </summary>
|
||||
public static Quaternion ComputeChainRotation(List<Transform> chain)
|
||||
{
|
||||
Quaternion total = Quaternion.identity;
|
||||
foreach (var t in chain)
|
||||
{
|
||||
total *= t.localRotation;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 초기 포즈(T-포즈) 캐싱 — Humanoid가 아니므로 직접 캐싱
|
||||
/// </summary>
|
||||
[HideInInspector] public Dictionary<string, Quaternion> restLocalRotations = new Dictionary<string, Quaternion>();
|
||||
[HideInInspector] public Dictionary<string, Vector3> restLocalPositions = new Dictionary<string, Vector3>();
|
||||
[HideInInspector] public bool isRestPoseCached = false;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 포즈를 기준 포즈(T-포즈)로 캐싱
|
||||
/// </summary>
|
||||
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}개 본");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 캐싱된 기준 포즈로 복원 (T-포즈 복원)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 본의 기준 포즈 로컬 회전 반환
|
||||
/// </summary>
|
||||
public Quaternion GetRestLocalRotation(string optiTrackBoneName)
|
||||
{
|
||||
if (restLocalRotations.TryGetValue(optiTrackBoneName, out Quaternion rot))
|
||||
return rot;
|
||||
return Quaternion.identity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -9,13 +9,14 @@ namespace KindRetargeting
|
||||
/// 이 스크립트는 원본 아바타(Source)의 포즈 손가락 움직임을 대상 아바타(Target)에 리타게팅(Retargeting)합니다.
|
||||
/// 또한 IK 타겟을 생성하여 대상 아바타의 관절 움직임을 자연스럽게 조정합니다.
|
||||
/// </summary>
|
||||
[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;
|
||||
/// <summary>
|
||||
/// 소스 본 Transform 접근 래퍼 (OptiTrack 매핑 사용)
|
||||
/// </summary>
|
||||
private Transform GetSourceBoneTransform(HumanBodyBones bone)
|
||||
{
|
||||
if (optitrackSource != null)
|
||||
return optitrackSource.GetBoneTransform(bone);
|
||||
return null;
|
||||
}
|
||||
|
||||
// OptiTrack 스파인/넥 분배용 캐시
|
||||
private List<Transform> sourceSpineChain; // 소스 스파인 체인
|
||||
private List<Transform> sourceNeckChain; // 소스 넥 체인
|
||||
private List<Transform> targetSpineBones; // 타겟 스파인 본들
|
||||
private List<Transform> targetNeckBones; // 타겟 넥 본들
|
||||
private List<Quaternion> spineOffsets; // T-포즈 기준: Inv(가상본 월드회전) * 타겟본 월드회전
|
||||
private List<Quaternion> 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<HumanBodyBones, Quaternion> rotationOffsets = new Dictionary<HumanBodyBones, Quaternion>();
|
||||
|
||||
// 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<RotationOffsetData> rotationOffsetCache;
|
||||
public float initialHipsHeight;
|
||||
public float avatarScale;
|
||||
@ -170,40 +183,6 @@ namespace KindRetargeting
|
||||
public float headScale;
|
||||
}
|
||||
|
||||
// CopyFingerPoseByMuscle에서 사용할 본 로컬 회전 저장용 (메모리 재사용)
|
||||
// 위치는 SetHumanPose가 처리하므로 회전만 저장
|
||||
private Dictionary<HumanBodyBones, Quaternion> savedBoneLocalRotations =
|
||||
new Dictionary<HumanBodyBones, Quaternion>();
|
||||
|
||||
// 손가락을 제외한 모든 휴먼본 목록 (캐싱)
|
||||
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<int, (int stretchedMuscle, int spreadMuscle)> fingerBoneToMuscleIndex = new Dictionary<int, (int, int)>
|
||||
{
|
||||
// 왼손 엄지 (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<int> thumbBoneIndices = new HashSet<int> { 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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출.
|
||||
/// </summary>
|
||||
private void EnsureSourcePoseCached()
|
||||
{
|
||||
if (!isSourcePoseCachedThisFrame && sourcePoseHandler != null)
|
||||
{
|
||||
sourcePoseHandler.GetHumanPose(ref sourcePose);
|
||||
isSourcePoseCachedThisFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 타겟 포즈를 캐싱하여 반환합니다. 프레임당 한 번만 GetHumanPose 호출.
|
||||
/// </summary>
|
||||
@ -496,9 +415,9 @@ namespace KindRetargeting
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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}°");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 머슬 데이터를 사용하여 손가락 포즈를 복제합니다.
|
||||
/// SetHumanPose가 모든 본에 영향을 미치므로, 손가락을 제외한 모든 본의 Transform을 저장하고 복원합니다.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 회전값을 사용하여 손가락 포즈를 복제합니다.
|
||||
/// </summary>
|
||||
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1149,7 +999,6 @@ namespace KindRetargeting
|
||||
/// <param name="skipBone">동기화에서 제외할 본</param>
|
||||
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 스파인/넥 분배
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 소스 감지 및 스파인/넥 분배 초기화
|
||||
/// </summary>
|
||||
private void InitializeOptiTrackSpineDistribution()
|
||||
{
|
||||
if (optitrackSource == null || targetAnimator == null)
|
||||
{
|
||||
useOptiTrackSpineDistribution = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 소스 스파인/넥 체인 가져오기
|
||||
sourceSpineChain = optitrackSource.GetSpineChainTransforms();
|
||||
sourceNeckChain = optitrackSource.GetNeckChainTransforms();
|
||||
|
||||
// 타겟 Humanoid 스파인 본들 수집 (존재하는 본만)
|
||||
targetSpineBones = new List<Transform>();
|
||||
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<Transform>();
|
||||
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}본");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// T-포즈 기준으로 가상 본의 월드 회전과 타겟 본의 월드 회전 사이 오프셋을 계산.
|
||||
/// offset = Inv(가상본 월드회전) * 타겟본 월드회전
|
||||
/// </summary>
|
||||
private List<Quaternion> CalculateVirtualBoneOffsets(List<Transform> sourceChain, List<Transform> targetBones)
|
||||
{
|
||||
var offsets = new List<Quaternion>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 분배 대상 본인지 확인 (스파인 + 넥)
|
||||
/// </summary>
|
||||
private bool IsOptiTrackDistributedBone(HumanBodyBones bone)
|
||||
{
|
||||
return bone == HumanBodyBones.Spine ||
|
||||
bone == HumanBodyBones.Chest ||
|
||||
bone == HumanBodyBones.UpperChest ||
|
||||
bone == HumanBodyBones.Neck;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스 스파인/넥 체인을 가상 본으로 그룹핑하여 월드 회전 + 오프셋으로 타겟에 적용
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스 체인을 가상 본으로 그룹핑 → 가상 본 월드 회전 계산 → 오프셋 적용하여 타겟에 세팅
|
||||
/// </summary>
|
||||
private void ApplyVirtualBoneRotations(List<Transform> sourceChain, List<Transform> targetBones, List<Quaternion> 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 타겟 생성 및 관관리
|
||||
|
||||
/// <summary>
|
||||
@ -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
|
||||
/// </summary>
|
||||
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);
|
||||
// 초기 스케일과 위치 저장
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -906,12 +906,12 @@ public class RetargetingControlWindow : EditorWindow
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user