implementation of drecon in unity 2022 lts
forked from:
https://github.com/joanllobera/marathon-envs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
18 KiB
433 lines
18 KiB
9 months ago
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using Unity.MLAgents;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Assertions;
|
||
|
using ManyWorlds;
|
||
|
|
||
|
public class Rewards2Learn : MonoBehaviour
|
||
|
{
|
||
|
[Header("Reward")]
|
||
|
public float SumOfSubRewards;
|
||
|
public float Reward;
|
||
|
|
||
|
[Header("Position Reward")]
|
||
|
public float SumOfDistances;
|
||
|
public float SumOfSqrDistances;
|
||
|
public float PositionReward;
|
||
|
|
||
|
[Header("Velocity Reward")]
|
||
|
public float PointsVelocityDifferenceSquared;
|
||
|
public float PointsVelocityReward;
|
||
|
|
||
|
[Header("Local Pose Reward")]
|
||
|
public List<float> RotationDifferences;
|
||
|
public float SumOfRotationDifferences;
|
||
|
public float SumOfRotationSqrDifferences;
|
||
|
public float LocalPoseReward;
|
||
|
|
||
|
|
||
|
|
||
|
[Header("Center of Mass Velocity Reward")]
|
||
|
public Vector3 MocapCOMVelocity;
|
||
|
public Vector3 RagDollCOMVelocity;
|
||
|
|
||
|
public float COMVelocityDifference;
|
||
|
public float ComVelocityReward;
|
||
|
public float ComReward;
|
||
|
|
||
|
[Header("Center of Mass Direction Reward")]
|
||
|
public float ComDirectionDistance;
|
||
|
public float ComDirectionReward;
|
||
|
|
||
|
[Header("Center of Mass Position Reward")]
|
||
|
public float ComDistance;
|
||
|
public float DistanceFactor;
|
||
|
public float ComPositionReward;
|
||
|
|
||
|
// [Header("Direction Factor")]
|
||
|
// public float DirectionDistance;
|
||
|
// public float DirectionFactor;
|
||
|
[Header("Velocity Difference Reward")]
|
||
|
public float VelDifferenceError;
|
||
|
public float VelAvDiffTwoStepsError;
|
||
|
public float VelAvDiffFourStepsError;
|
||
|
public float VelDifferenceReward;
|
||
|
public float VelDiffTwoStepsReward;
|
||
|
public float VelDiffFourStepsReward;
|
||
|
float [][] _velHistoryTwoSteps;
|
||
|
float [][] _velHistoryFourSteps;
|
||
|
|
||
|
|
||
|
[Header("Minimize Energy Reward")]
|
||
|
public float KineticEnergyMetric;
|
||
|
public float EnergyMinimReward;
|
||
|
|
||
|
|
||
|
|
||
|
[Header("Misc")]
|
||
|
public float HeadHeightDistance;
|
||
|
|
||
|
[Header("Gizmos")]
|
||
|
public int ObjectForPointDistancesGizmo;
|
||
|
|
||
|
SpawnableEnv _spawnableEnv;
|
||
|
MapAnim2Ragdoll _mocap;
|
||
|
GameObject _ragDoll;
|
||
|
InputController _inputController;
|
||
|
|
||
|
internal RewardStats _mocapBodyStats;
|
||
|
internal RewardStats _ragDollBodyStats;
|
||
|
|
||
|
// List<ArticulationBody> _mocapBodyParts;
|
||
|
// List<ArticulationBody> _ragDollBodyParts;
|
||
|
Transform _mocapHead;
|
||
|
Transform _ragDollHead;
|
||
|
|
||
|
bool _hasLazyInitialized;
|
||
|
bool _reproduceDReCon;
|
||
|
|
||
|
[Header("Things to check for rewards")]
|
||
|
public string headname = "head";
|
||
|
|
||
|
public string targetedRootName = "articulation:Hips";
|
||
|
|
||
|
|
||
|
public void OnAgentInitialize(bool reproduceDReCon)
|
||
|
{
|
||
|
Assert.IsFalse(_hasLazyInitialized);
|
||
|
|
||
|
_hasLazyInitialized = true;
|
||
|
_reproduceDReCon = reproduceDReCon;
|
||
|
|
||
|
_spawnableEnv = GetComponentInParent<SpawnableEnv>();
|
||
|
Assert.IsNotNull(_spawnableEnv);
|
||
|
|
||
|
_mocap = _spawnableEnv.GetComponentInChildren<MapAnim2Ragdoll>();
|
||
|
|
||
|
_ragDoll = _spawnableEnv.GetComponentInChildren<ProcRagdollAgent>().gameObject;
|
||
|
Assert.IsNotNull(_mocap);
|
||
|
Assert.IsNotNull(_ragDoll);
|
||
|
_inputController = _spawnableEnv.GetComponentInChildren<InputController>();
|
||
|
// _mocapBodyParts = _mocap.GetComponentsInChildren<ArticulationBody>().ToList();
|
||
|
// _ragDollBodyParts = _ragDoll.GetComponentsInChildren<ArticulationBody>().ToList();
|
||
|
// Assert.AreEqual(_mocapBodyParts.Count, _ragDollBodyParts.Count);
|
||
|
_mocapHead = _mocap
|
||
|
.GetComponentsInChildren<Transform>()
|
||
|
.First(x => x.name == headname);
|
||
|
_ragDollHead = _ragDoll
|
||
|
.GetComponentsInChildren<Transform>()
|
||
|
.First(x => x.name == headname);
|
||
|
_mocapBodyStats = new GameObject("MocapDReConRewardStats").AddComponent<RewardStats>();
|
||
|
_mocapBodyStats.setRootName(targetedRootName);
|
||
|
_mocapBodyStats.setHeadName(headname);
|
||
|
|
||
|
_mocapBodyStats.ObjectToTrack = _mocap;
|
||
|
|
||
|
_mocapBodyStats.transform.SetParent(_spawnableEnv.transform);
|
||
|
_mocapBodyStats.OnAgentInitialize(_mocapBodyStats.ObjectToTrack.transform);
|
||
|
|
||
|
_ragDollBodyStats = new GameObject("RagDollDReConRewardStats").AddComponent<RewardStats>();
|
||
|
_ragDollBodyStats.setRootName(targetedRootName);
|
||
|
_ragDollBodyStats.setHeadName(headname);
|
||
|
|
||
|
|
||
|
_ragDollBodyStats.ObjectToTrack = this;
|
||
|
_ragDollBodyStats.transform.SetParent(_spawnableEnv.transform);
|
||
|
_ragDollBodyStats.OnAgentInitialize(transform, _mocapBodyStats);
|
||
|
|
||
|
_velHistoryTwoSteps = new float[][]{
|
||
|
_ragDollBodyStats.PointVelocity
|
||
|
.Zip(_mocapBodyStats.PointVelocity, (x,y) => x.magnitude-y.magnitude)
|
||
|
.ToArray(),
|
||
|
_ragDollBodyStats.PointVelocity
|
||
|
.Zip(_mocapBodyStats.PointVelocity, (x,y) => x.magnitude-y.magnitude)
|
||
|
.ToArray()
|
||
|
};
|
||
|
_velHistoryFourSteps = new float[][]{
|
||
|
_ragDollBodyStats.PointVelocity
|
||
|
.Zip(_mocapBodyStats.PointVelocity, (x,y) => x.magnitude-y.magnitude)
|
||
|
.ToArray(),
|
||
|
_ragDollBodyStats.PointVelocity
|
||
|
.Zip(_mocapBodyStats.PointVelocity, (x,y) => x.magnitude-y.magnitude)
|
||
|
.ToArray(),
|
||
|
_ragDollBodyStats.PointVelocity
|
||
|
.Zip(_mocapBodyStats.PointVelocity, (x,y) => x.magnitude-y.magnitude)
|
||
|
.ToArray(),
|
||
|
_ragDollBodyStats.PointVelocity
|
||
|
.Zip(_mocapBodyStats.PointVelocity, (x,y) => x.magnitude-y.magnitude)
|
||
|
.ToArray()
|
||
|
};
|
||
|
|
||
|
_mocapBodyStats.AssertIsCompatible(_ragDollBodyStats);
|
||
|
}
|
||
|
|
||
|
// Update is called once per frame
|
||
|
public void OnStep(float timeDelta)
|
||
|
{
|
||
|
_mocapBodyStats.SetStatusForStep(timeDelta);
|
||
|
_ragDollBodyStats.SetStatusForStep(timeDelta);
|
||
|
|
||
|
if (_reproduceDReCon)
|
||
|
{
|
||
|
DReConRewards(timeDelta);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// deep sort scales
|
||
|
float num_points = _ragDollBodyStats.Points.Length;
|
||
|
float num_joints = num_points / 6f;
|
||
|
float pose_scale = 2.0f / 15f * num_joints;
|
||
|
float vel_scale = 0.1f / 15f * num_joints;
|
||
|
// note is 10 in code, but 40 in paper.
|
||
|
const float position_scale = 10f; // was end_eff_scale
|
||
|
// const float root_scale = 5f;
|
||
|
const float com_scale = 5f;
|
||
|
|
||
|
// DeepMimic
|
||
|
// const float pose_w = 0.5f;
|
||
|
// const float vel_w = 0.05f;
|
||
|
// const float position_w = 0.15f; // was end_eff_w
|
||
|
// // const float root_w = 0.2f;
|
||
|
// const float com_w = 0.2f; // * 2
|
||
|
// const float energy_w = 0.2f;
|
||
|
|
||
|
// // UniCon
|
||
|
// const float pose_w = 0.4f;
|
||
|
// const float vel_w = 0.1f;
|
||
|
// const float position_w = 0.1f;
|
||
|
// // const float com_direction_w = 0.1f;
|
||
|
// const float com_direction_w = 0.2f;
|
||
|
// const float com_velocity_w = 0.1f;
|
||
|
// const float energy_w = 0.2f;
|
||
|
|
||
|
// MarCon
|
||
|
const float pose_w = 0.2f;
|
||
|
const float position_w = 0.2f;
|
||
|
const float com_direction_w = 0.1f;
|
||
|
const float com_velocity_w = 0.3f;
|
||
|
const float energy_w = 0.2f;
|
||
|
|
||
|
// position reward
|
||
|
List<float> distances = _mocapBodyStats.GetPointDistancesFrom(_ragDollBodyStats);
|
||
|
List<float> sqrDistances = distances.Select(x=> x*x).ToList();
|
||
|
SumOfDistances = distances.Sum();
|
||
|
SumOfSqrDistances = sqrDistances.Sum();
|
||
|
var deepMimicScaledDistance = SumOfSqrDistances / (num_points / 4);
|
||
|
PositionReward = Mathf.Exp(-position_scale * (deepMimicScaledDistance));
|
||
|
if (PositionReward == 0f)
|
||
|
{
|
||
|
PositionReward = 0f;
|
||
|
}
|
||
|
|
||
|
// center of mass velocity reward
|
||
|
MocapCOMVelocity = _mocapBodyStats.CenterOfMassVelocity;
|
||
|
RagDollCOMVelocity = _ragDollBodyStats.CenterOfMassVelocity;
|
||
|
COMVelocityDifference = (MocapCOMVelocity-RagDollCOMVelocity).magnitude;
|
||
|
ComVelocityReward = Mathf.Pow(COMVelocityDifference,2);
|
||
|
ComVelocityReward = Mathf.Exp(-com_scale*ComVelocityReward);
|
||
|
|
||
|
// points velocity
|
||
|
List<float> velocityDistances = _mocapBodyStats.GetPointVelocityDistancesFrom(_ragDollBodyStats);
|
||
|
List<float> sqrVelocityDistances = velocityDistances.Select(x=> x*x).ToList();
|
||
|
PointsVelocityDifferenceSquared = sqrVelocityDistances.Sum();
|
||
|
var deepMimicScaledVelocityDistanceSquared = PointsVelocityDifferenceSquared / (num_points / 4);
|
||
|
PointsVelocityReward = Mathf.Exp(-vel_scale * deepMimicScaledVelocityDistanceSquared);
|
||
|
|
||
|
// local pose reward
|
||
|
if (RotationDifferences == null || RotationDifferences.Count < _mocapBodyStats.Rotations.Count)
|
||
|
RotationDifferences = Enumerable.Range(0,_mocapBodyStats.Rotations.Count)
|
||
|
.Select(x=>0f)
|
||
|
.ToList();
|
||
|
SumOfRotationDifferences = 0f;
|
||
|
SumOfRotationSqrDifferences = 0f;
|
||
|
for (int i = 0; i < _mocapBodyStats.Rotations.Count; i++)
|
||
|
{
|
||
|
var angle = Quaternion.Angle(_mocapBodyStats.Rotations[i], _ragDollBodyStats.Rotations[i]);
|
||
|
Assert.IsTrue(angle <= 180f);
|
||
|
angle = DReConObservationStats.NormalizedAngle(angle);
|
||
|
angle = Mathf.Abs(angle);
|
||
|
angle = angle / Mathf.PI;
|
||
|
var sqrAngle = angle * angle;
|
||
|
RotationDifferences[i] = angle;
|
||
|
SumOfRotationDifferences += angle;
|
||
|
SumOfRotationSqrDifferences += sqrAngle;
|
||
|
}
|
||
|
LocalPoseReward = Mathf.Exp(-pose_scale * SumOfRotationSqrDifferences);
|
||
|
|
||
|
// center of mass direction reward (from 0f to 1f)
|
||
|
ComDirectionDistance = Vector3.Dot(
|
||
|
_mocapBodyStats.transform.forward,
|
||
|
_ragDollBodyStats.transform.forward);
|
||
|
ComDirectionDistance = 1f-((ComDirectionDistance + 1f)/2f);
|
||
|
ComDirectionReward = ComDirectionDistance;
|
||
|
ComDirectionReward = Mathf.Exp(-com_scale*ComDirectionReward);
|
||
|
|
||
|
// // COM velocity factor
|
||
|
// var comVelocityFactor = COMVelocityDifference;
|
||
|
// comVelocityFactor = comVelocityFactor / 2f;
|
||
|
// comVelocityFactor = 1.01f - comVelocityFactor;
|
||
|
// comVelocityFactor = Mathf.Clamp(comVelocityFactor, 0f, 1f);
|
||
|
|
||
|
// Calc Velocity difference Error
|
||
|
VelDifferenceError = _ragDollBodyStats.PointVelocity
|
||
|
.Zip(_mocapBodyStats.PointVelocity, (x,y) => (x-y).magnitude)
|
||
|
.Average();
|
||
|
VelDifferenceError = Mathf.Abs(VelDifferenceError);
|
||
|
VelDifferenceReward = Mathf.Exp(-10f * VelDifferenceError);
|
||
|
VelDifferenceReward = Mathf.Clamp(VelDifferenceReward, 0f, 1f);
|
||
|
// roll history
|
||
|
var oldestTwoSteps = _velHistoryTwoSteps[1];
|
||
|
_velHistoryTwoSteps[1] = _velHistoryTwoSteps[0];
|
||
|
_velHistoryTwoSteps[0] = oldestTwoSteps;
|
||
|
var oldestFourSteps = _velHistoryFourSteps[3];
|
||
|
_velHistoryFourSteps[3] = _velHistoryFourSteps[2];
|
||
|
_velHistoryFourSteps[2] = _velHistoryFourSteps[1];
|
||
|
_velHistoryFourSteps[1] = _velHistoryFourSteps[0];
|
||
|
_velHistoryFourSteps[0] = oldestFourSteps;
|
||
|
int velRows = _ragDollBodyStats.PointVelocity.Length;
|
||
|
for (int i = 0; i < velRows; i++)
|
||
|
{
|
||
|
var diff = _ragDollBodyStats.PointVelocity[i]-_mocapBodyStats.PointVelocity[i];
|
||
|
var sqDiff = diff.magnitude*diff.magnitude;
|
||
|
var absDiff = Mathf.Abs(diff.magnitude);
|
||
|
oldestTwoSteps[i] = absDiff;
|
||
|
oldestFourSteps[i] = absDiff;
|
||
|
// oldestTwoSteps[i] = sqDiff;
|
||
|
// oldestFourSteps[i] = sqDiff;
|
||
|
}
|
||
|
VelAvDiffTwoStepsError =
|
||
|
_velHistoryTwoSteps[0].Sum() +
|
||
|
_velHistoryTwoSteps[1].Sum();
|
||
|
VelAvDiffTwoStepsError /= velRows*2f;
|
||
|
VelAvDiffFourStepsError =
|
||
|
_velHistoryFourSteps[0].Sum() +
|
||
|
_velHistoryFourSteps[1].Sum() +
|
||
|
_velHistoryFourSteps[2].Sum() +
|
||
|
_velHistoryFourSteps[3].Sum();
|
||
|
VelAvDiffFourStepsError /= velRows*4f;
|
||
|
VelDiffTwoStepsReward = Mathf.Exp(-10 * VelAvDiffTwoStepsError);
|
||
|
VelDiffFourStepsReward = Mathf.Exp(-10 * VelAvDiffFourStepsError);
|
||
|
|
||
|
// calculate energy:
|
||
|
//we obviate the masses, we want this to be important all across
|
||
|
List<float> es = _ragDollBodyStats.PointVelocity.Select(x => x.magnitude * x.magnitude).ToList<float>();
|
||
|
KineticEnergyMetric = es.Sum() / es.Count;
|
||
|
//a quick run suggests the input values range between 0 and 10, with most values near 0, so a simple way to get a reward value between 0 and 1 seems:
|
||
|
EnergyMinimReward = Mathf.Exp(-KineticEnergyMetric);
|
||
|
|
||
|
|
||
|
// misc
|
||
|
HeadHeightDistance = (_mocapHead.position.y - _ragDollHead.position.y);
|
||
|
HeadHeightDistance = Mathf.Abs(HeadHeightDistance);
|
||
|
|
||
|
// reward
|
||
|
// SumOfSubRewards = ComPositionReward+ComVelocityReward+ComDirectionReward+PositionReward+LocalPoseReward+PointsVelocityReward+VelDifferenceReward;
|
||
|
SumOfSubRewards = ComVelocityReward+ComDirectionReward+PositionReward+LocalPoseReward+VelDifferenceReward;
|
||
|
Reward = 0f +
|
||
|
(ComVelocityReward * com_velocity_w) + // com_w) +
|
||
|
(ComDirectionReward * com_direction_w) + // com_w) +
|
||
|
(PositionReward * position_w) +
|
||
|
(LocalPoseReward * pose_w) +
|
||
|
// (PointsVelocityReward * vel_w) +
|
||
|
(VelDifferenceReward * energy_w);
|
||
|
|
||
|
|
||
|
// Reward = Reward + EnergyMinimReward;
|
||
|
|
||
|
// var sqrtComVelocityReward = Mathf.Sqrt(ComVelocityReward);
|
||
|
// var sqrtComDirectionReward = Mathf.Sqrt(ComDirectionReward);
|
||
|
// Reward *= (sqrtComVelocityReward*sqrtComDirectionReward);
|
||
|
}
|
||
|
|
||
|
void DReConRewards(float timeDelta)
|
||
|
{
|
||
|
// position reward
|
||
|
List<float> distances = _mocapBodyStats.GetPointDistancesFrom(_ragDollBodyStats);
|
||
|
PositionReward = -7.37f / (distances.Count / 6f);
|
||
|
List<float> sqrDistances = distances.Select(x => x * x).ToList();
|
||
|
SumOfDistances = distances.Sum();
|
||
|
SumOfSqrDistances = sqrDistances.Sum();
|
||
|
PositionReward *= SumOfSqrDistances;
|
||
|
PositionReward = Mathf.Exp(PositionReward);
|
||
|
|
||
|
// center of mass velocity reward
|
||
|
MocapCOMVelocity = _mocapBodyStats.CenterOfMassVelocity;
|
||
|
RagDollCOMVelocity = _ragDollBodyStats.CenterOfMassVelocity;
|
||
|
COMVelocityDifference = (MocapCOMVelocity - RagDollCOMVelocity).magnitude;
|
||
|
ComReward = -Mathf.Pow(COMVelocityDifference, 2);
|
||
|
ComReward = Mathf.Exp(ComReward);
|
||
|
|
||
|
// points velocity
|
||
|
List<float> velocityDistances = _mocapBodyStats.GetPointVelocityDistancesFrom(_ragDollBodyStats);
|
||
|
List<float> sqrVelocityDistances = velocityDistances.Select(x => x * x).ToList();
|
||
|
PointsVelocityDifferenceSquared = sqrVelocityDistances.Sum();
|
||
|
PointsVelocityReward = (-1f / _mocapBodyStats.PointVelocity.Length) * PointsVelocityDifferenceSquared;
|
||
|
PointsVelocityReward = Mathf.Exp(PointsVelocityReward);
|
||
|
|
||
|
// local pose reward
|
||
|
if (RotationDifferences == null || RotationDifferences.Count < _mocapBodyStats.Rotations.Count)
|
||
|
RotationDifferences = Enumerable.Range(0, _mocapBodyStats.Rotations.Count)
|
||
|
.Select(x => 0f)
|
||
|
.ToList();
|
||
|
SumOfRotationDifferences = 0f;
|
||
|
SumOfRotationSqrDifferences = 0f;
|
||
|
for (int i = 0; i < _mocapBodyStats.Rotations.Count; i++)
|
||
|
{
|
||
|
var angle = Quaternion.Angle(_mocapBodyStats.Rotations[i], _ragDollBodyStats.Rotations[i]);
|
||
|
Assert.IsTrue(angle <= 180f);
|
||
|
angle = ObservationStats.NormalizedAngle(angle);
|
||
|
var sqrAngle = angle * angle;
|
||
|
RotationDifferences[i] = angle;
|
||
|
SumOfRotationDifferences += angle;
|
||
|
SumOfRotationSqrDifferences += sqrAngle;
|
||
|
}
|
||
|
LocalPoseReward = -6.5f / RotationDifferences.Count;
|
||
|
LocalPoseReward *= SumOfRotationSqrDifferences;
|
||
|
LocalPoseReward = Mathf.Exp(LocalPoseReward);
|
||
|
|
||
|
// distance factor
|
||
|
ComDistance = (_mocapBodyStats.LastCenterOfMassInWorldSpace - _ragDollBodyStats.LastCenterOfMassInWorldSpace).magnitude;
|
||
|
HeadHeightDistance = (_mocapHead.position.y - _ragDollHead.position.y);
|
||
|
HeadHeightDistance = Mathf.Abs(HeadHeightDistance);
|
||
|
var headDistance = (_mocapBodyStats.HeadPositionInWorldSpace - _ragDollBodyStats.HeadPositionInWorldSpace).magnitude;
|
||
|
DistanceFactor = Mathf.Pow(headDistance, 2);
|
||
|
DistanceFactor = 1.4f * DistanceFactor;
|
||
|
DistanceFactor = 1.3f - DistanceFactor;
|
||
|
DistanceFactor = Mathf.Clamp(DistanceFactor, 0f, 1f);
|
||
|
|
||
|
// // direction factor
|
||
|
// Vector3 desiredDirection = _inputController.HorizontalDirection;
|
||
|
// var curDirection = _ragDollBodyStats.transform.forward;
|
||
|
// // cosAngle
|
||
|
// var directionDifference = Vector3.Dot(desiredDirection, curDirection);
|
||
|
// DirectionDistance = (1f + directionDifference) /2f; // normalize the error
|
||
|
// DirectionFactor = Mathf.Pow(DirectionDistance,2);
|
||
|
// DirectionFactor = Mathf.Clamp(DirectionFactor, 0f, 1f);
|
||
|
|
||
|
// misc
|
||
|
|
||
|
// reward
|
||
|
SumOfSubRewards = PositionReward + ComReward + PointsVelocityReward + LocalPoseReward;
|
||
|
Reward = DistanceFactor * SumOfSubRewards;
|
||
|
// Reward = (DirectionFactor*SumOfSubRewards) * DistanceFactor;
|
||
|
}
|
||
|
public void OnReset()
|
||
|
{
|
||
|
Assert.IsTrue(_hasLazyInitialized);
|
||
|
|
||
|
_mocapBodyStats.OnReset();
|
||
|
_ragDollBodyStats.OnReset();
|
||
|
}
|
||
|
void OnDrawGizmos()
|
||
|
{
|
||
|
if (_ragDollBodyStats == null)
|
||
|
return;
|
||
|
var max = (_ragDollBodyStats.Points.Length / 6) - 1;
|
||
|
ObjectForPointDistancesGizmo = Mathf.Clamp(ObjectForPointDistancesGizmo, -1, max);
|
||
|
// _mocapBodyStats.DrawPointDistancesFrom(_ragDollBodyStats, ObjectForPointDistancesGizmo);
|
||
|
_ragDollBodyStats.DrawPointDistancesFrom(_mocapBodyStats, ObjectForPointDistancesGizmo);
|
||
|
}
|
||
|
}
|