organisation & splat changes
This commit is contained in:
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyConfig.cs
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyConfig.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using static BodyHelper002;
|
||||
|
||||
public class BodyConfig
|
||||
{
|
||||
public Func<string, BodyPartGroup> GetBodyPartGroup;
|
||||
public Func<string, MuscleGroup> GetMuscleGroup;
|
||||
public Func<BodyPartGroup> GetRootBodyPart;
|
||||
public Func<MuscleGroup> GetRootMuscle;
|
||||
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyConfig.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyConfig.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03e1cab076cd94a488d1decc5d843de7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyHelper002.cs
Normal file
33
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyHelper002.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
public static class BodyHelper002
|
||||
{
|
||||
[System.Serializable]
|
||||
public enum BodyPartGroup {
|
||||
None,
|
||||
Hips,
|
||||
Torso,
|
||||
Spine,
|
||||
Head,
|
||||
ArmUpper,
|
||||
ArmLower,
|
||||
Hand,
|
||||
LegUpper,
|
||||
LegLower,
|
||||
Foot,
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public enum MuscleGroup {
|
||||
None,
|
||||
Hips,
|
||||
Torso,
|
||||
Spine,
|
||||
Head,
|
||||
ArmUpper,
|
||||
ArmLower,
|
||||
Hand,
|
||||
LegUpper,
|
||||
LegLower,
|
||||
Foot,
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyHelper002.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyHelper002.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf75e597c351b12498957c0ec1d646fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
594
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyManager002.cs
Normal file
594
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyManager002.cs
Normal file
@@ -0,0 +1,594 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.MLAgents;
|
||||
using Unity.MLAgents.Actuators;
|
||||
using Unity.MLAgents.Sensors;
|
||||
using System.Linq;
|
||||
using static BodyHelper002;
|
||||
using System;
|
||||
using ManyWorlds;
|
||||
|
||||
public class BodyManager002 : MonoBehaviour, IOnSensorCollision
|
||||
{
|
||||
// Options / Configurables global properties
|
||||
public Transform CameraTarget;
|
||||
public float FixedDeltaTime = 0.005f;
|
||||
public bool ShowMonitor = false;
|
||||
public bool DebugDisableMotor;
|
||||
public bool DebugShowWithOffset;
|
||||
|
||||
|
||||
// Observations / Read only global properties
|
||||
public List<Muscle002> Muscles;
|
||||
public List<BodyPart002> BodyParts;
|
||||
public List<float> SensorIsInTouch;
|
||||
public List<float> Observations;
|
||||
public int ObservationNormalizedErrors;
|
||||
public int MaxObservationNormalizedErrors;
|
||||
public List<GameObject> Sensors;
|
||||
public float FrameReward;
|
||||
public float AverageReward;
|
||||
|
||||
|
||||
// private properties
|
||||
Vector3 startPosition;
|
||||
|
||||
Dictionary<GameObject, Vector3> transformsPosition;
|
||||
Dictionary<GameObject, Quaternion> transformsRotation;
|
||||
|
||||
Agent _agent;
|
||||
SpawnableEnv _spawnableEnv;
|
||||
TerrainGenerator _terrainGenerator;
|
||||
DecisionRequester _decisionRequester;
|
||||
|
||||
static int _startCount;
|
||||
|
||||
float[] lastVectorAction;
|
||||
float[] vectorDifference;
|
||||
List <Vector3> mphBuffer;
|
||||
|
||||
[Tooltip("Max distance travelled across all episodes")]
|
||||
/**< \brief Max distance travelled across all episodes*/
|
||||
public float MaxDistanceTraveled;
|
||||
|
||||
[Tooltip("Distance travelled this episode")]
|
||||
/**< \brief Distance travelled this episode*/
|
||||
public float DistanceTraveled;
|
||||
|
||||
List<SphereCollider> sensorColliders;
|
||||
static int _spawnCount;
|
||||
|
||||
// static ScoreHistogramData _scoreHistogramData;
|
||||
|
||||
|
||||
// void FixedUpdate()
|
||||
// {
|
||||
// foreach (var muscle in Muscles)
|
||||
// {
|
||||
// // var i = Muscles.IndexOf(muscle);
|
||||
// // muscle.UpdateObservations();
|
||||
// // if (!DebugShowWithOffset && !DebugDisableMotor)
|
||||
// // muscle.UpdateMotor();
|
||||
// // if (!muscle.Rigidbody.useGravity)
|
||||
// // continue; // skip sub joints
|
||||
// // }
|
||||
// }
|
||||
|
||||
public BodyConfig BodyConfig;
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnInitializeAgent()
|
||||
{
|
||||
_spawnableEnv = GetComponentInParent<SpawnableEnv>();
|
||||
_terrainGenerator = GetComponentInParent<TerrainGenerator>();
|
||||
SetupBody();
|
||||
DistanceTraveled = float.MinValue;
|
||||
}
|
||||
|
||||
public void OnAgentReset()
|
||||
{
|
||||
if (DistanceTraveled != float.MinValue)
|
||||
{
|
||||
var scorer = FindObjectOfType<Scorer>();
|
||||
scorer?.ReportScore(DistanceTraveled, "Distance Traveled");
|
||||
}
|
||||
HandleModelReset();
|
||||
Sensors = _agent.GetComponentsInChildren<SensorBehavior>()
|
||||
.Select(x=>x.gameObject)
|
||||
.ToList();
|
||||
sensorColliders = Sensors
|
||||
.Select(x=>x.GetComponent<SphereCollider>())
|
||||
.ToList();
|
||||
SensorIsInTouch = Enumerable.Range(0,Sensors.Count).Select(x=>0f).ToList();
|
||||
// HACK first spawned agent should grab the camera
|
||||
var smoothFollow = GameObject.FindObjectOfType<SmoothFollow>();
|
||||
if (smoothFollow != null && smoothFollow.target == null) {
|
||||
if (_spawnCount == 0) // HACK follow nth agent
|
||||
{
|
||||
smoothFollow.target = CameraTarget;
|
||||
ShowMonitor = true;
|
||||
}
|
||||
else
|
||||
_spawnCount++;
|
||||
}
|
||||
lastVectorAction = null;
|
||||
vectorDifference = null;
|
||||
mphBuffer = new List<Vector3>();
|
||||
}
|
||||
|
||||
public void OnAgentAction(float[] vectorAction)
|
||||
{
|
||||
if (lastVectorAction == null){
|
||||
lastVectorAction = vectorAction.Select(x=>0f).ToArray();
|
||||
vectorDifference = vectorAction.Select(x=>0f).ToArray();
|
||||
}
|
||||
int i = 0;
|
||||
foreach (var muscle in Muscles)
|
||||
{
|
||||
// if(muscle.Parent == null)
|
||||
// continue;
|
||||
if (muscle.ConfigurableJoint.angularXMotion != ConfigurableJointMotion.Locked){
|
||||
vectorDifference[i] = Mathf.Abs(vectorAction[i]-lastVectorAction[i]);
|
||||
muscle.TargetNormalizedRotationX = vectorAction[i++];
|
||||
}
|
||||
if (muscle.ConfigurableJoint.angularYMotion != ConfigurableJointMotion.Locked){
|
||||
vectorDifference[i] = Mathf.Abs(vectorAction[i]-lastVectorAction[i]);
|
||||
muscle.TargetNormalizedRotationY = vectorAction[i++];
|
||||
}
|
||||
if (muscle.ConfigurableJoint.angularZMotion != ConfigurableJointMotion.Locked){
|
||||
vectorDifference[i] = Mathf.Abs(vectorAction[i]-lastVectorAction[i]);
|
||||
muscle.TargetNormalizedRotationZ = vectorAction[i++];
|
||||
}
|
||||
if (!DebugDisableMotor)
|
||||
muscle.UpdateMotor();
|
||||
}
|
||||
|
||||
if (ShowMonitor)
|
||||
{
|
||||
// var hist = new[] {velocity, uprightBonus, heightPenality, effort}.ToList();
|
||||
// Monitor.Log("rewardHist", hist.ToArray(), displayType: Monitor.DisplayType.Independent);
|
||||
}
|
||||
}
|
||||
|
||||
public BodyPart002 GetFirstBodyPart(BodyPartGroup bodyPartGroup)
|
||||
{
|
||||
var bodyPart = BodyParts.FirstOrDefault(x=>x.Group == bodyPartGroup);
|
||||
return bodyPart;
|
||||
}
|
||||
public List<BodyPart002> GetBodyParts()
|
||||
{
|
||||
return BodyParts;
|
||||
}
|
||||
public List<BodyPart002> GetBodyParts(BodyPartGroup bodyPartGroup)
|
||||
{
|
||||
return BodyParts.Where(x=>x.Group == bodyPartGroup).ToList();
|
||||
}
|
||||
|
||||
public float GetActionDifference()
|
||||
{
|
||||
float actionDifference = 1f - vectorDifference.Average();
|
||||
actionDifference = Mathf.Clamp(actionDifference, 0, 1);
|
||||
actionDifference = Mathf.Pow(actionDifference,2);
|
||||
return actionDifference;
|
||||
}
|
||||
|
||||
void SetupBody()
|
||||
{
|
||||
_agent = GetComponent<Agent>();
|
||||
_decisionRequester = GetComponent<DecisionRequester>();
|
||||
Time.fixedDeltaTime = FixedDeltaTime;
|
||||
|
||||
BodyParts = new List<BodyPart002> ();
|
||||
BodyPart002 root = null;
|
||||
foreach (var t in GetComponentsInChildren<Transform>())
|
||||
{
|
||||
if (BodyConfig.GetBodyPartGroup(t.name) == BodyHelper002.BodyPartGroup.None)
|
||||
continue;
|
||||
|
||||
var bodyPart = new BodyPart002{
|
||||
Rigidbody = t.GetComponent<Rigidbody>(),
|
||||
Transform = t,
|
||||
Name = t.name,
|
||||
Group = BodyConfig.GetBodyPartGroup(t.name),
|
||||
};
|
||||
if (bodyPart.Group == BodyConfig.GetRootBodyPart())
|
||||
root = bodyPart;
|
||||
bodyPart.Root = root;
|
||||
bodyPart.Init();
|
||||
BodyParts.Add(bodyPart);
|
||||
}
|
||||
var partCount = BodyParts.Count;
|
||||
|
||||
Muscles = new List<Muscle002> ();
|
||||
var muscles = GetComponentsInChildren<ConfigurableJoint>();
|
||||
ConfigurableJoint rootConfigurableJoint = null;
|
||||
var ragDoll = GetComponent<RagDoll002>();
|
||||
foreach (var m in muscles)
|
||||
{
|
||||
var maximumForce = ragDoll.MusclePowers.First(x=>x.Muscle == m.name).PowerVector;
|
||||
maximumForce *= ragDoll.MotorScale;
|
||||
var muscle = new Muscle002{
|
||||
Rigidbody = m.GetComponent<Rigidbody>(),
|
||||
Transform = m.GetComponent<Transform>(),
|
||||
ConfigurableJoint = m,
|
||||
Name = m.name,
|
||||
Group = BodyConfig.GetMuscleGroup(m.name),
|
||||
MaximumForce = maximumForce
|
||||
};
|
||||
if (muscle.Group == BodyConfig.GetRootMuscle())
|
||||
rootConfigurableJoint = muscle.ConfigurableJoint;
|
||||
muscle.RootConfigurableJoint = rootConfigurableJoint;
|
||||
muscle.Init();
|
||||
|
||||
Muscles.Add(muscle);
|
||||
}
|
||||
_startCount++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HandleModelReset()
|
||||
{
|
||||
Transform[] allChildren = _agent.GetComponentsInChildren<Transform>();
|
||||
if (transformsPosition != null)
|
||||
{
|
||||
foreach (var child in allChildren)
|
||||
{
|
||||
child.position = transformsPosition[child.gameObject];
|
||||
child.rotation = transformsRotation[child.gameObject];
|
||||
var childRb = child.GetComponent<Rigidbody>();
|
||||
if (childRb != null)
|
||||
{
|
||||
childRb.angularVelocity = Vector3.zero;
|
||||
childRb.velocity = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
startPosition = _agent.transform.position;
|
||||
transformsPosition = new Dictionary<GameObject, Vector3>();
|
||||
transformsRotation = new Dictionary<GameObject, Quaternion>();
|
||||
foreach (Transform child in allChildren)
|
||||
{
|
||||
transformsPosition[child.gameObject] = child.position;
|
||||
transformsRotation[child.gameObject] = child.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetHeightNormalizedReward(float maxHeight)
|
||||
{
|
||||
var height = GetHeight();
|
||||
var heightPenality = maxHeight - height;
|
||||
heightPenality = Mathf.Clamp(heightPenality, 0f, maxHeight);
|
||||
var reward = 1f - heightPenality;
|
||||
reward = Mathf.Clamp(reward, 0f, 1f);
|
||||
return reward;
|
||||
}
|
||||
internal float GetHeight()
|
||||
{
|
||||
var feetYpos = BodyParts
|
||||
.Where(x => x.Group == BodyPartGroup.Foot)
|
||||
.Select(x => x.Transform.position.y)
|
||||
.OrderBy(x => x)
|
||||
.ToList();
|
||||
float lowestFoot = 0f;
|
||||
if (feetYpos != null && feetYpos.Count != 0)
|
||||
lowestFoot = feetYpos[0];
|
||||
var height = GetFirstBodyPart(BodyPartGroup.Head).Transform.position.y - lowestFoot;
|
||||
return height;
|
||||
}
|
||||
public float GetDirectionNormalizedReward(BodyPartGroup bodyPartGroup, Vector3 direction)
|
||||
{
|
||||
BodyPart002 bodyPart = GetFirstBodyPart(bodyPartGroup);
|
||||
float maxBonus = 1f;
|
||||
var toFocalAngle = bodyPart.ToFocalRoation * bodyPart.Transform.right;
|
||||
var angle = Vector3.Angle(toFocalAngle, direction);
|
||||
var qpos2 = (angle % 180) / 180;
|
||||
var bonus = maxBonus * (2 - (Mathf.Abs(qpos2) * 2) - 1);
|
||||
return bonus;
|
||||
}
|
||||
|
||||
public float GetUprightNormalizedReward(BodyPartGroup bodyPartGroup)
|
||||
{
|
||||
BodyPart002 bodyPart = GetFirstBodyPart(bodyPartGroup);
|
||||
float maxBonus = 1f;
|
||||
var toFocalAngle = bodyPart.ToFocalRoation * -bodyPart.Transform.forward;
|
||||
var angleFromUp = Vector3.Angle(toFocalAngle, Vector3.up);
|
||||
var qpos2 = (angleFromUp % 180) / 180;
|
||||
var uprightBonus = maxBonus * (2 - (Mathf.Abs(qpos2) * 2) - 1);
|
||||
return uprightBonus;
|
||||
}
|
||||
public float GetEffortNormalized(string[] ignorJoints = null)
|
||||
{
|
||||
double effort = 0;
|
||||
double jointEffort = 0;
|
||||
double joints = 0;
|
||||
foreach (var muscle in Muscles)
|
||||
{
|
||||
if(muscle.Parent == null)
|
||||
continue;
|
||||
var name = muscle.Name;
|
||||
if (ignorJoints != null && ignorJoints.Contains(name))
|
||||
continue;
|
||||
if (muscle.ConfigurableJoint.angularXMotion != ConfigurableJointMotion.Locked) {
|
||||
jointEffort = Mathf.Pow(Mathf.Abs(muscle.TargetNormalizedRotationX),2);
|
||||
effort += jointEffort;
|
||||
joints++;
|
||||
}
|
||||
if (muscle.ConfigurableJoint.angularYMotion != ConfigurableJointMotion.Locked) {
|
||||
jointEffort = Mathf.Pow(Mathf.Abs(muscle.TargetNormalizedRotationY),2);
|
||||
effort += jointEffort;
|
||||
joints++;
|
||||
}
|
||||
if (muscle.ConfigurableJoint.angularZMotion != ConfigurableJointMotion.Locked) {
|
||||
jointEffort = Mathf.Pow(Mathf.Abs(muscle.TargetNormalizedRotationZ),2);
|
||||
effort += jointEffort;
|
||||
joints++;
|
||||
}
|
||||
}
|
||||
|
||||
return (float) (effort / joints);
|
||||
}
|
||||
public void OnSensorCollisionEnter(Collider sensorCollider, GameObject other) {
|
||||
// if (string.Compare(other.name, "Terrain", true) !=0)
|
||||
if (other.GetComponent<Terrain>() == null)
|
||||
return;
|
||||
var sensor = Sensors
|
||||
.FirstOrDefault(x=>x == sensorCollider.gameObject);
|
||||
if (sensor != null) {
|
||||
var idx = Sensors.IndexOf(sensor);
|
||||
SensorIsInTouch[idx] = 1f;
|
||||
}
|
||||
}
|
||||
public void OnSensorCollisionExit(Collider sensorCollider, GameObject other)
|
||||
{
|
||||
// if (string.Compare(other.gameObject.name, "Terrain", true) !=0)
|
||||
if (other.GetComponent<Terrain>() == null)
|
||||
return;
|
||||
var sensor = Sensors
|
||||
.FirstOrDefault(x=>x == sensorCollider.gameObject);
|
||||
if (sensor != null) {
|
||||
var idx = Sensors.IndexOf(sensor);
|
||||
SensorIsInTouch[idx] = 0f;
|
||||
}
|
||||
}
|
||||
public Vector3 GetLocalCenterOfMass()
|
||||
{
|
||||
var centerOfMass = GetCenterOfMass();
|
||||
centerOfMass -= transform.position;
|
||||
return centerOfMass;
|
||||
}
|
||||
public Vector3 GetCenterOfMass()
|
||||
{
|
||||
var centerOfMass = Vector3.zero;
|
||||
float totalMass = 0f;
|
||||
var bodies = BodyParts
|
||||
.Select(x=>x.Rigidbody)
|
||||
.Where(x=>x!=null)
|
||||
.ToList();
|
||||
foreach (Rigidbody rb in bodies)
|
||||
{
|
||||
centerOfMass += rb.worldCenterOfMass * rb.mass;
|
||||
totalMass += rb.mass;
|
||||
}
|
||||
centerOfMass /= totalMass;
|
||||
return centerOfMass;
|
||||
}
|
||||
|
||||
public Vector3 GetNormalizedVelocity()
|
||||
{
|
||||
var pelvis = GetFirstBodyPart(BodyConfig.GetRootBodyPart());
|
||||
Vector3 metersPerSecond = pelvis.Rigidbody.velocity;
|
||||
var n = GetNormalizedVelocity(metersPerSecond);
|
||||
return n;
|
||||
}
|
||||
|
||||
public Vector3 GetNormalizedPosition()
|
||||
{
|
||||
// var position = GetCenterOfMass();
|
||||
var pelvis = GetFirstBodyPart(BodyConfig.GetRootBodyPart());
|
||||
var position = pelvis.Transform.position;
|
||||
var normalizedPosition = GetNormalizedPosition(position - startPosition);
|
||||
return normalizedPosition;
|
||||
}
|
||||
public void SetDebugFrameReward(float reward)
|
||||
{
|
||||
FrameReward = reward;
|
||||
var stepCount = _agent.StepCount > 0 ? _agent.StepCount : 1;
|
||||
if (_decisionRequester?.DecisionPeriod > 1)
|
||||
stepCount /= _decisionRequester.DecisionPeriod;
|
||||
AverageReward = _agent.GetCumulativeReward() / (float) stepCount;
|
||||
}
|
||||
|
||||
|
||||
public List<float> GetSensorIsInTouch()
|
||||
{
|
||||
return SensorIsInTouch;
|
||||
}
|
||||
// public List<float> GetBodyPartsObservations()
|
||||
// {
|
||||
// List<float> vectorObservation = new List<float>();
|
||||
// foreach (var bodyPart in BodyParts)
|
||||
// {
|
||||
// bodyPart.UpdateObservations();
|
||||
// // _agent.sensor.AddObservation(bodyPart.ObsRotation);
|
||||
// vectorObservation.Add(bodyPart.ObsRotation.x);
|
||||
// vectorObservation.Add(bodyPart.ObsRotation.y);
|
||||
// vectorObservation.Add(bodyPart.ObsRotation.z);
|
||||
// vectorObservation.Add(bodyPart.ObsRotation.w);
|
||||
|
||||
// // _agent.sensor.AddObservation(bodyPart.ObsRotationVelocity);
|
||||
// vectorObservation.Add(bodyPart.ObsRotationVelocity.x);
|
||||
// vectorObservation.Add(bodyPart.ObsRotationVelocity.y);
|
||||
// vectorObservation.Add(bodyPart.ObsRotationVelocity.z);
|
||||
|
||||
// // _agent.sensor.AddObservation(GetNormalizedVelocity(bodyPart.ObsVelocity));
|
||||
// var normalizedVelocity = GetNormalizedVelocity(bodyPart.ObsVelocity);
|
||||
// vectorObservation.Add(normalizedVelocity.x);
|
||||
// vectorObservation.Add(normalizedVelocity.y);
|
||||
// vectorObservation.Add(normalizedVelocity.z);
|
||||
// }
|
||||
// return vectorObservation;
|
||||
// }
|
||||
public List<float> GetMusclesObservations()
|
||||
{
|
||||
List<float> vectorObservation = new List<float>();
|
||||
foreach (var muscle in Muscles)
|
||||
{
|
||||
muscle.UpdateObservations();
|
||||
if (muscle.ConfigurableJoint.angularXMotion != ConfigurableJointMotion.Locked)
|
||||
vectorObservation.Add(muscle.TargetNormalizedRotationX);
|
||||
if (muscle.ConfigurableJoint.angularYMotion != ConfigurableJointMotion.Locked)
|
||||
vectorObservation.Add(muscle.TargetNormalizedRotationY);
|
||||
if (muscle.ConfigurableJoint.angularZMotion != ConfigurableJointMotion.Locked)
|
||||
vectorObservation.Add(muscle.TargetNormalizedRotationZ);
|
||||
}
|
||||
return vectorObservation;
|
||||
}
|
||||
[Obsolete("use GetSensorObservations()")]
|
||||
public List<float> GetSensorYPositions()
|
||||
{
|
||||
var sensorYpositions = Sensors
|
||||
.Select(x=> this.GetNormalizedPosition(x.transform.position - startPosition))
|
||||
.Select(x=>x.y)
|
||||
.ToList();
|
||||
return sensorYpositions;
|
||||
}
|
||||
[Obsolete("use GetSensorObservations()")]
|
||||
public List<float> GetSensorZPositions()
|
||||
{
|
||||
var sensorYpositions = Sensors
|
||||
.Select(x=> this.GetNormalizedPosition(x.transform.position - startPosition))
|
||||
.Select(x=>x.z)
|
||||
.ToList();
|
||||
return sensorYpositions;
|
||||
}
|
||||
|
||||
public List<float> GetSensorObservations()
|
||||
{
|
||||
var localSensorsPos = new Vector3[Sensors.Count];
|
||||
var globalSensorsPos = new Vector3[Sensors.Count];
|
||||
for (int i = 0; i < Sensors.Count; i++) {
|
||||
globalSensorsPos[i] = sensorColliders[i].transform.TransformPoint(sensorColliders[i].center);
|
||||
localSensorsPos[i] = globalSensorsPos[i] - startPosition;
|
||||
}
|
||||
|
||||
// get heights based on global senor position
|
||||
var sensorsPos = Sensors
|
||||
.Select(x=>x.transform.position).ToList();
|
||||
var senorHeights = _terrainGenerator != null
|
||||
? _terrainGenerator.GetDistances2d(globalSensorsPos)
|
||||
: Enumerable.Range(0, globalSensorsPos.Length).Select(x=>0f).ToList();
|
||||
for (int i = 0; i < Sensors.Count; i++) {
|
||||
senorHeights[i] -= sensorColliders[i].radius;
|
||||
if (senorHeights[i] >= 1f)
|
||||
senorHeights[i] = 1f;
|
||||
}
|
||||
|
||||
// get z positions based on local positions
|
||||
var bounds = _spawnableEnv.bounds;
|
||||
var normalizedZ = localSensorsPos
|
||||
.Select(x=>x.z / (bounds.extents.z))
|
||||
.ToList();
|
||||
var observations = senorHeights
|
||||
.Concat(normalizedZ)
|
||||
.ToList();
|
||||
return observations;
|
||||
}
|
||||
|
||||
// public void OnCollectObservationsHandleDebug(AgentInfo info)
|
||||
// {
|
||||
// if (Observations?.Count != info.vectorObservation.Count)
|
||||
// Observations = Enumerable.Range(0, info.vectorObservation.Count).Select(x => 0f).ToList();
|
||||
// ObservationNormalizedErrors = 0;
|
||||
// for (int i = 0; i < Observations.Count; i++)
|
||||
// {
|
||||
// Observations[i] = info.vectorObservation[i];
|
||||
// var x = Mathf.Abs(Observations[i]);
|
||||
// var e = Mathf.Epsilon;
|
||||
// bool is1 = Mathf.Approximately(x, 1f);
|
||||
// if ((x > 1f + e) && !is1)
|
||||
// ObservationNormalizedErrors++;
|
||||
// }
|
||||
// if (ObservationNormalizedErrors > MaxObservationNormalizedErrors)
|
||||
// MaxObservationNormalizedErrors = ObservationNormalizedErrors;
|
||||
|
||||
// var pelvis = GetFirstBodyPart(BodyPartGroup.Hips);
|
||||
// DistanceTraveled = pelvis.Transform.position.x;
|
||||
// MaxDistanceTraveled = Mathf.Max(MaxDistanceTraveled, DistanceTraveled);
|
||||
// Vector3 metersPerSecond = pelvis.Rigidbody.velocity;
|
||||
// Vector3 mph = metersPerSecond * 2.236936f;
|
||||
// mphBuffer.Add(mph);
|
||||
// if (mphBuffer.Count > 100)
|
||||
// mphBuffer.RemoveAt(0);
|
||||
// var aveMph = new Vector3(
|
||||
// mphBuffer.Select(x=>x.x).Average(),
|
||||
// mphBuffer.Select(x=>x.y).Average(),
|
||||
// mphBuffer.Select(x=>x.z).Average()
|
||||
// );
|
||||
// if (ShowMonitor)
|
||||
// {
|
||||
// Monitor.Log("MaxDistance", MaxDistanceTraveled.ToString());
|
||||
// Monitor.Log("NormalizedPos", GetNormalizedPosition().ToString());
|
||||
// Monitor.Log("MPH: ", (aveMph).ToString());
|
||||
// }
|
||||
// }
|
||||
|
||||
float NextGaussian(float mu = 0, float sigma = 1)
|
||||
{
|
||||
var u1 = UnityEngine.Random.value;
|
||||
var u2 = UnityEngine.Random.value;
|
||||
|
||||
var rand_std_normal = Mathf.Sqrt(-2.0f * Mathf.Log(u1)) *
|
||||
Mathf.Sin(2.0f * Mathf.PI * u2);
|
||||
|
||||
var rand_normal = mu + sigma * rand_std_normal;
|
||||
|
||||
return rand_normal;
|
||||
}
|
||||
public Vector3 GetNormalizedVelocity(Vector3 metersPerSecond)
|
||||
{
|
||||
var maxMetersPerSecond = _spawnableEnv.bounds.size
|
||||
/ _agent.MaxStep
|
||||
/ Time.fixedDeltaTime;
|
||||
var maxXZ = Mathf.Max(maxMetersPerSecond.x, maxMetersPerSecond.z);
|
||||
maxMetersPerSecond.x = maxXZ;
|
||||
maxMetersPerSecond.z = maxXZ;
|
||||
maxMetersPerSecond.y = 53; // override with
|
||||
float x = metersPerSecond.x / maxMetersPerSecond.x;
|
||||
float y = metersPerSecond.y / maxMetersPerSecond.y;
|
||||
float z = metersPerSecond.z / maxMetersPerSecond.z;
|
||||
// clamp result
|
||||
x = Mathf.Clamp(x, -1f, 1f);
|
||||
y = Mathf.Clamp(y, -1f, 1f);
|
||||
z = Mathf.Clamp(z, -1f, 1f);
|
||||
Vector3 normalizedVelocity = new Vector3(x,y,z);
|
||||
return normalizedVelocity;
|
||||
}
|
||||
public Vector3 GetNormalizedPosition(Vector3 pos)
|
||||
{
|
||||
var maxPos = _spawnableEnv.bounds.size;
|
||||
float x = pos.x / maxPos.x;
|
||||
float y = pos.y / maxPos.y;
|
||||
float z = pos.z / maxPos.z;
|
||||
// clamp result
|
||||
x = Mathf.Clamp(x, -1f, 1f);
|
||||
y = Mathf.Clamp(y, -1f, 1f);
|
||||
z = Mathf.Clamp(z, -1f, 1f);
|
||||
Vector3 normalizedPos = new Vector3(x,y,z);
|
||||
return normalizedPos;
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyManager002.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyManager002.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 776799f55314f4500bc35f859c94e2fe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
239
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyPart002.cs
Normal file
239
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyPart002.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
// A class that describes a body part of an Agent or an Animator. The object
|
||||
// stores the rigid body associated with an Agent's body part. Along with some
|
||||
// important data like root bone, i. e. 'butt' for a marathonMan. Implments methods
|
||||
// to calculate distance to the characteristics of an animation being mimiced.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Unity.MLAgents;
|
||||
|
||||
|
||||
[System.Serializable]
|
||||
public class BodyPart002
|
||||
{
|
||||
|
||||
public string Name;
|
||||
public BodyHelper002.BodyPartGroup Group;
|
||||
|
||||
public Vector3 ObsLocalPosition;
|
||||
public Quaternion ObsRotation;
|
||||
public Quaternion ObsRotationFromBase;
|
||||
public Vector3 ObsRotationVelocity;
|
||||
public Vector3 ObsVelocity;
|
||||
public float ObsAngleDeltaFromAnimationRotation;
|
||||
public Vector3 ObsDeltaFromAnimationPosition;
|
||||
|
||||
public Vector3 ObsDeltaFromAnimationVelocity;
|
||||
public Vector3 ObsDeltaFromAnimationAngularVelocity;
|
||||
public Vector3 ObsDeltaFromAnimationAngularVelocityWorld;
|
||||
public Vector3 DebugMaxRotationVelocity;
|
||||
public Vector3 DebugMaxVelocity;
|
||||
|
||||
public Quaternion DefaultLocalRotation;
|
||||
public Quaternion ToJointSpaceInverse;
|
||||
public Quaternion ToJointSpaceDefault;
|
||||
|
||||
public Rigidbody Rigidbody;
|
||||
public Transform Transform;
|
||||
public BodyPart002 Root;
|
||||
public Quaternion InitialRootRotation;
|
||||
public Vector3 InitialRootPosition;
|
||||
|
||||
// base = from where to measure rotation and position from
|
||||
public Quaternion BaseRotation;
|
||||
public Vector3 BasePosition;
|
||||
//
|
||||
public Quaternion ToFocalRoation;
|
||||
|
||||
|
||||
Quaternion _lastObsRotation;
|
||||
Quaternion _lastWorldRotation;
|
||||
Vector3 _lastLocalPosition;
|
||||
Vector3 _lastWorldPosition;
|
||||
Vector3 _animationAngularVelocity;
|
||||
Vector3 _animationAngularVelocityWorld;
|
||||
Vector3 _animationVelocityWorld;
|
||||
|
||||
DecisionRequester _decisionRequester;
|
||||
|
||||
float _lastUpdateObsTime;
|
||||
bool _firstRunComplete;
|
||||
bool _hasRanVeryFirstInit;
|
||||
private Vector3 _animationPositionWorld;
|
||||
private Quaternion _animationRotation;
|
||||
|
||||
static Vector3 Vector3Max (Vector3 a, Vector3 b)
|
||||
{
|
||||
var answer = new Vector3(
|
||||
Mathf.Max(Mathf.Abs(a.x), Mathf.Abs(b.x)),
|
||||
Mathf.Max(Mathf.Abs(a.y), Mathf.Abs(b.y)),
|
||||
Mathf.Max(Mathf.Abs(a.z), Mathf.Abs(b.z)));
|
||||
return answer;
|
||||
}
|
||||
|
||||
// Initialize
|
||||
public void Init()
|
||||
{
|
||||
_decisionRequester = GameObject.Find("MarathonMan").GetComponent<DecisionRequester>();
|
||||
|
||||
_firstRunComplete = false;
|
||||
if (Rigidbody != null){
|
||||
Rigidbody.angularVelocity = Vector3.zero;
|
||||
Rigidbody.velocity = Vector3.zero;
|
||||
}
|
||||
|
||||
if (!_hasRanVeryFirstInit) {
|
||||
|
||||
InitialRootRotation = Root.Transform.transform.rotation;
|
||||
InitialRootPosition = Root.Transform.transform.position;
|
||||
BaseRotation = Root.Transform.transform.rotation;
|
||||
BasePosition = Root.Transform.transform.position;
|
||||
|
||||
DefaultLocalRotation = LocalRotation;
|
||||
Vector3 forward = this.Transform.forward;
|
||||
Vector3 up = this.Transform.up;
|
||||
Quaternion toJointSpace = Quaternion.LookRotation(forward, up);
|
||||
|
||||
ToJointSpaceInverse = Quaternion.Inverse(toJointSpace);
|
||||
ToJointSpaceDefault = DefaultLocalRotation * toJointSpace;
|
||||
|
||||
// set body part direction
|
||||
Vector3 focalOffset = new Vector3(10,0,0);
|
||||
if (Rigidbody != null){
|
||||
var focalPoint = Rigidbody.position + focalOffset;
|
||||
ToFocalRoation = Rigidbody.rotation;
|
||||
ToFocalRoation.SetLookRotation(focalPoint - Rigidbody.position);
|
||||
}
|
||||
|
||||
_hasRanVeryFirstInit = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Update values like ObsLocalPosition, ObsRotation, ... that will be accessed
|
||||
// by the agent as observations for the neural network. Also calculates distances
|
||||
// to an animation being mimicked
|
||||
public void UpdateObservations()
|
||||
{
|
||||
Quaternion rotation;
|
||||
Vector3 position;
|
||||
if (this == Root) {
|
||||
rotation = Quaternion.Inverse(InitialRootRotation) * Transform.rotation;
|
||||
position = Transform.position - InitialRootPosition;
|
||||
}
|
||||
else {
|
||||
rotation = Quaternion.Inverse(Root.Transform.rotation) * Transform.rotation;
|
||||
position = Transform.position - Root.Transform.position;
|
||||
}
|
||||
|
||||
if (_firstRunComplete == false){
|
||||
_lastUpdateObsTime = Time.time;
|
||||
_lastLocalPosition = position;
|
||||
_lastWorldPosition = Transform.position;
|
||||
_lastObsRotation = rotation;
|
||||
_lastWorldRotation = Transform.rotation;
|
||||
}
|
||||
|
||||
var dt = Time.fixedDeltaTime * _decisionRequester.DecisionPeriod;
|
||||
|
||||
var velocity = (position - _lastLocalPosition)/dt;
|
||||
var velocityWorld = (Transform.position - _lastWorldPosition)/dt;
|
||||
var angularVelocity = JointHelper002.CalcDeltaRotationNormalizedEuler(_lastObsRotation, rotation)/dt;
|
||||
var angularVelocityWorld = JointHelper002.CalcDeltaRotationNormalizedEuler(_lastWorldRotation, Transform.rotation)/dt;
|
||||
|
||||
// old calulation for observation vector
|
||||
//angularVelocity = NormalizedEulerAngles(rotationVelocity.eulerAngles);
|
||||
//angularVelocity /= 128f;
|
||||
// old calculation end
|
||||
|
||||
_lastUpdateObsTime = Time.time;
|
||||
_lastLocalPosition = position;
|
||||
_lastWorldPosition = Transform.position;
|
||||
_lastObsRotation = rotation;
|
||||
_lastWorldRotation = Transform.rotation;
|
||||
|
||||
//if (Name == "right_right_foot") {
|
||||
// Debug.Log("^^^^^^^^^^^^");
|
||||
// Debug.Log("body part name: " + Name);
|
||||
// Debug.Log("animation angular velocity:" + _animationAngularVelocity);
|
||||
// Debug.Log("angular velocity:" + angularVelocity);
|
||||
// Debug.Log("animation angular velocity world:" + _animationAngularVelocityWorld);
|
||||
// Debug.Log("angular velocity world:" + angularVelocityWorld);
|
||||
// Debug.Log("rotation local:" + rotation.eulerAngles);
|
||||
// Debug.Log("animation rotation local: " + _animationRotation.eulerAngles);
|
||||
// Debug.Log("velocity world: " + velocityWorld);
|
||||
// Debug.Log("animation velocity world:" + _animationVelocityWorld);
|
||||
// Debug.Log("transform position:" + Transform.position);
|
||||
// Debug.Log("animation position world: " + _animationPositionWorld);
|
||||
// Debug.Log("dt:" + dt);
|
||||
//}
|
||||
|
||||
ObsLocalPosition = position;
|
||||
ObsRotation = rotation;
|
||||
ObsRotationVelocity = angularVelocity;
|
||||
ObsVelocity = velocity;
|
||||
|
||||
ObsDeltaFromAnimationPosition = _animationPositionWorld - Transform.position;
|
||||
|
||||
ObsAngleDeltaFromAnimationRotation = Quaternion.Angle(_animationRotation, rotation);
|
||||
ObsAngleDeltaFromAnimationRotation = JointHelper002.NormalizedAngle(ObsAngleDeltaFromAnimationRotation);
|
||||
|
||||
ObsDeltaFromAnimationVelocity = _animationVelocityWorld - velocityWorld;
|
||||
ObsDeltaFromAnimationAngularVelocity = (_animationAngularVelocity - angularVelocity);
|
||||
ObsDeltaFromAnimationAngularVelocityWorld = (_animationAngularVelocityWorld - angularVelocityWorld);
|
||||
|
||||
DebugMaxRotationVelocity = Vector3Max(DebugMaxRotationVelocity, angularVelocity);
|
||||
DebugMaxVelocity = Vector3Max(DebugMaxVelocity, velocity);
|
||||
|
||||
_firstRunComplete = true;
|
||||
}
|
||||
|
||||
// returns local rotation
|
||||
public Quaternion LocalRotation {
|
||||
get {
|
||||
return Quaternion.Inverse(RootRotation) * Transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
// retuns root rotation
|
||||
public Quaternion RootRotation{
|
||||
get {
|
||||
return InitialRootRotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the position, rotation of a body part to match animation's position and
|
||||
// rotation. Also sets the rigid body's position and velocity, which is used
|
||||
// in angular momentum calculation
|
||||
public void MoveToAnim(Vector3 animPosition, Quaternion animRotation, Vector3 angularVelocity, Vector3 velocity)
|
||||
{
|
||||
Transform.position = animPosition;
|
||||
Transform.rotation = animRotation;
|
||||
if (Rigidbody != null){
|
||||
foreach (var childRb in Rigidbody.GetComponentsInChildren<Rigidbody>())
|
||||
{
|
||||
if (childRb == Rigidbody)
|
||||
continue;
|
||||
childRb.transform.localPosition = Vector3.zero;
|
||||
childRb.transform.localEulerAngles = Vector3.zero;
|
||||
childRb.angularVelocity = Vector3.zero;
|
||||
childRb.velocity = Vector3.zero;
|
||||
}
|
||||
Rigidbody.angularVelocity = angularVelocity;
|
||||
Rigidbody.velocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the position of the animation being mimicked
|
||||
public void SetAnimationPosition(Vector3 animPositionWorld, Quaternion animRotationLocal, Vector3 animVelocityWorld, Vector3 animAngularVelocityLocal, Vector3 animAngularVelocityWorld)
|
||||
{
|
||||
_animationPositionWorld = animPositionWorld;
|
||||
_animationRotation = animRotationLocal;
|
||||
|
||||
_animationVelocityWorld = animVelocityWorld;
|
||||
_animationAngularVelocity = animAngularVelocityLocal;
|
||||
_animationAngularVelocityWorld = animAngularVelocityWorld;
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyPart002.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/BodyPart002.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6e9d3ad85936e6459222f0017b88b80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.MLAgents;
|
||||
using UnityEngine;
|
||||
|
||||
public class DomainRandomization : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/DomainRandomization.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/DomainRandomization.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e04e2081c5c5647a9993dcdc05765cd0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
Assets/3_MarathonEnvs/Scripts/Ragdoll002/JointHelper002.cs
Normal file
107
Assets/3_MarathonEnvs/Scripts/Ragdoll002/JointHelper002.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Helper Utilities to work with agent's rigid bodies charateristics. Allows to
|
||||
// calculate Angles between rotations in radians, find center of mass of an agent,
|
||||
// and find Angular Momentum of an agent.
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
public static class JointHelper002 {
|
||||
|
||||
// Return rotation from one rotation to another
|
||||
public static Quaternion FromToRotation(Quaternion from, Quaternion to) {
|
||||
if (to == from) return Quaternion.identity;
|
||||
|
||||
return to * Quaternion.Inverse(from);
|
||||
}
|
||||
|
||||
// Calculate rotation between two rotations in radians. Adjusts the value to lie within [-pi, +pi].
|
||||
public static Vector3 NormalizedEulerAngles(Vector3 eulerAngles) {
|
||||
var x = eulerAngles.x < 180f ?
|
||||
eulerAngles.x :
|
||||
-360 + eulerAngles.x;
|
||||
var y = eulerAngles.y < 180f ?
|
||||
eulerAngles.y :
|
||||
-360 + eulerAngles.y;
|
||||
var z = eulerAngles.z < 180f ?
|
||||
eulerAngles.z :
|
||||
-360 + eulerAngles.z;
|
||||
x = x * Mathf.Deg2Rad;
|
||||
y = y * Mathf.Deg2Rad;
|
||||
z = z * Mathf.Deg2Rad;
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
// Adjust the value of an angle to lie within [-pi, +pi].
|
||||
public static float NormalizedAngle(float angle) {
|
||||
if (angle < 180) {
|
||||
return angle * Mathf.Deg2Rad;
|
||||
}
|
||||
return (angle - 360) * Mathf.Deg2Rad;
|
||||
}
|
||||
|
||||
// Find rotation and convert to radians within [-pi, +pi].
|
||||
public static Vector3 CalcDeltaRotationNormalizedEuler(Quaternion from, Quaternion to) {
|
||||
var rotationVelocity = FromToRotation(from, to);
|
||||
var angularVelocity = NormalizedEulerAngles(rotationVelocity.eulerAngles);
|
||||
return angularVelocity;
|
||||
}
|
||||
|
||||
// Find the center of mass of a list of Body Parts beloning to an agent. Relative to the root bone, i. e. "butt" for humanoid.
|
||||
public static Vector3 GetCenterOfMassRelativeToRoot(List<BodyPart002> BodyParts) {
|
||||
var centerOfMass = Vector3.zero;
|
||||
float totalMass = 0f;
|
||||
var bodies = BodyParts
|
||||
.Select(x => x.Rigidbody)
|
||||
.Where(x => x != null)
|
||||
.ToList();
|
||||
var rootBone = BodyParts[0];
|
||||
foreach (Rigidbody rb in bodies) {
|
||||
centerOfMass += rb.worldCenterOfMass * rb.mass;
|
||||
totalMass += rb.mass;
|
||||
}
|
||||
centerOfMass /= totalMass;
|
||||
centerOfMass -= rootBone.InitialRootPosition;
|
||||
return centerOfMass;
|
||||
}
|
||||
|
||||
// Find the center of mass of a List of Body Parts relative to the world coordinate system.
|
||||
public static Vector3 GetCenterOfMassWorld(List<BodyPart002> BodyParts) {
|
||||
var centerOfMass = GetCenterOfMassRelativeToRoot(BodyParts) + BodyParts[0].InitialRootPosition;
|
||||
return centerOfMass;
|
||||
}
|
||||
|
||||
// Calculate Angular Momentum of a List of Body Parts. In the world coordinate system about the center
|
||||
// of mass of the Body Parts. Formulas at https://ocw.mit.edu/courses/aeronautics-and-astronautics/16-07-dynamics-fall-2009/lecture-notes/MIT16_07F09_Lec11.pdf
|
||||
public static Vector3 GetAngularMoment(List<BodyPart002> BodyParts) {
|
||||
var centerOfMass = GetCenterOfMassWorld(BodyParts);
|
||||
var bodies = BodyParts
|
||||
.Select(x => x.Rigidbody)
|
||||
.Where(x => x != null)
|
||||
.ToList();
|
||||
Vector3 totalAngularMoment = Vector3.zero;
|
||||
foreach (Rigidbody rb in bodies) {
|
||||
|
||||
var w_local = rb.transform.rotation * rb.angularVelocity;
|
||||
var w_inertiaFrame = rb.inertiaTensorRotation * w_local;
|
||||
|
||||
Vector3 L_inertiaFrame = Vector3.zero;
|
||||
L_inertiaFrame[0] = w_inertiaFrame[0] * rb.inertiaTensor[0];
|
||||
L_inertiaFrame[1] = w_inertiaFrame[1] * rb.inertiaTensor[1];
|
||||
L_inertiaFrame[2] = w_inertiaFrame[2] * rb.inertiaTensor[2];
|
||||
|
||||
Vector3 L_world = Quaternion.Inverse(rb.transform.rotation) * Quaternion.Inverse(rb.inertiaTensorRotation) * L_inertiaFrame;
|
||||
|
||||
Vector3 bodyPartCenterOfMassRelativeTobodyPartsCenterOfMass = rb.worldCenterOfMass - centerOfMass;
|
||||
Vector3 LofBodyPartCenterOfMass = rb.mass * Vector3.Cross(bodyPartCenterOfMassRelativeTobodyPartsCenterOfMass, rb.velocity);
|
||||
|
||||
totalAngularMoment += L_world + LofBodyPartCenterOfMass;
|
||||
|
||||
}
|
||||
return totalAngularMoment;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/JointHelper002.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/JointHelper002.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 168784be71eb147fd92601f19593c1b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
195
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonHelper.cs
Normal file
195
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonHelper.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.MLAgents
|
||||
{
|
||||
public static class MarathonHelper
|
||||
{
|
||||
static readonly bool FlipMarathonX = false;
|
||||
|
||||
public static Vector3 RightToLeft(Vector3 rightHanded, bool hackFlipZ = false)
|
||||
{
|
||||
if (FlipMarathonX)
|
||||
return new Vector3(rightHanded.x, rightHanded.z,
|
||||
rightHanded.y); // use if fliping marathon's X direction
|
||||
return new Vector3(-rightHanded.x, rightHanded.z, -rightHanded.y); // use to maintain marathon's X direction
|
||||
}
|
||||
|
||||
static char[] _delimiterChars = {' ', ',', ':', '\t'};
|
||||
|
||||
static string RemoveDuplicateWhitespace(string input)
|
||||
{
|
||||
while (input.Contains(" "))
|
||||
input = input.Replace(" ", " ");
|
||||
while (input.Contains("\t\t"))
|
||||
input = input.Replace("\t\t", "\t");
|
||||
return input;
|
||||
}
|
||||
|
||||
static float Evaluate(string expression)
|
||||
{
|
||||
var doc = new System.Xml.XPath.XPathDocument(new System.IO.StringReader("<r/>"));
|
||||
var nav = doc.CreateNavigator();
|
||||
var newString = expression;
|
||||
newString = (new System.Text.RegularExpressions.Regex(@"([\+\-\*])")).Replace(newString, " ${1} ");
|
||||
newString = newString.Replace("/", " div ").Replace("%", " mod ");
|
||||
var res = nav.Evaluate("number(" + newString + ")");
|
||||
double d = (double) res;
|
||||
return (float) d;
|
||||
}
|
||||
|
||||
|
||||
static public Vector3 ParseVector3NoFlipYZ(string str)
|
||||
{
|
||||
str = RemoveDuplicateWhitespace(str);
|
||||
string[] words = str.Split(_delimiterChars);
|
||||
float x = Evaluate(words[0]);
|
||||
float y = Evaluate(words[1]);
|
||||
float z = Evaluate(words[2]);
|
||||
var vec3 = new Vector3(x, y, z);
|
||||
return vec3;
|
||||
}
|
||||
|
||||
static public Quaternion ParseQuaternion(string str)
|
||||
{
|
||||
str = RemoveDuplicateWhitespace(str);
|
||||
string[] words = str.Split(_delimiterChars);
|
||||
float w = Evaluate(words[0]);
|
||||
float x = Evaluate(words[1]);
|
||||
float y = Evaluate(words[2]);
|
||||
float z = Evaluate(words[3]);
|
||||
var q = new Quaternion(x, y, z, w);
|
||||
return q;
|
||||
}
|
||||
|
||||
static public Vector3 ParseAxis(string str)
|
||||
{
|
||||
var axis = MarathonHelper.ParseVector3NoFlipYZ(str);
|
||||
if (FlipMarathonX)
|
||||
axis = new Vector3(-axis.x, -axis.z, -axis.y); // use if fliping marathon's X direction
|
||||
else
|
||||
axis = new Vector3(axis.x, -axis.z, axis.y); // use to maintain marathon's X direction
|
||||
return axis;
|
||||
}
|
||||
|
||||
static public Vector3 JointParsePosition(string str, bool hackFlipZ)
|
||||
{
|
||||
str = RemoveDuplicateWhitespace(str);
|
||||
string[] words = str.Split(_delimiterChars);
|
||||
float x = Evaluate(words[0]);
|
||||
float y = Evaluate(words[1]);
|
||||
float z = Evaluate(words[2]);
|
||||
Vector3 vec3 = new Vector3(x, y, z);
|
||||
return RightToLeft(vec3, hackFlipZ);
|
||||
}
|
||||
|
||||
public static Vector3 ParsePosition(string str)
|
||||
{
|
||||
str = RemoveDuplicateWhitespace(str);
|
||||
string[] words = str.Split(_delimiterChars);
|
||||
float x = Evaluate(words[0]);
|
||||
float y = Evaluate(words[1]);
|
||||
float z = Evaluate(words[2]);
|
||||
Vector3 vec3 = new Vector3(x, y, z);
|
||||
return RightToLeft(vec3);
|
||||
}
|
||||
|
||||
public static Vector3 ParseFrom(string fromTo)
|
||||
{
|
||||
return ParseVector3NoFlipYZ(fromTo);
|
||||
}
|
||||
|
||||
public static Vector3 ParseTo(string fromTo)
|
||||
{
|
||||
fromTo = RemoveDuplicateWhitespace(fromTo);
|
||||
string[] words = fromTo.Split(_delimiterChars);
|
||||
float x = Evaluate(words[3]);
|
||||
float y = Evaluate(words[4]);
|
||||
float z = Evaluate(words[5]);
|
||||
Vector3 vec3 = new Vector3(x, y, z);
|
||||
return vec3;
|
||||
}
|
||||
|
||||
public static Vector2 ParseVector2(string str)
|
||||
{
|
||||
str = RemoveDuplicateWhitespace(str);
|
||||
string[] words = str.Split(_delimiterChars);
|
||||
float x = Evaluate(words[0]);
|
||||
float y = Evaluate(words[1]);
|
||||
var vec2 = new Vector2(x, y);
|
||||
return vec2;
|
||||
}
|
||||
|
||||
public static float ParseGetMin(string rangeAsText)
|
||||
{
|
||||
rangeAsText = RemoveDuplicateWhitespace(rangeAsText);
|
||||
string[] words = rangeAsText.Split(_delimiterChars);
|
||||
var range = words.Select(x => Evaluate(x));
|
||||
return range.Min();
|
||||
}
|
||||
|
||||
public static float ParseGetMax(string rangeAsText)
|
||||
{
|
||||
rangeAsText = RemoveDuplicateWhitespace(rangeAsText);
|
||||
string[] words = rangeAsText.Split(_delimiterChars);
|
||||
var range = words.Select(x => Evaluate(x));
|
||||
return range.Max();
|
||||
}
|
||||
|
||||
|
||||
public static GameObject CreateBetweenPoints(this GameObject parent, Vector3 start, Vector3 end, float width,
|
||||
bool useWorldSpace, GameObject root)
|
||||
{
|
||||
start = RightToLeft(start);
|
||||
end = RightToLeft(end);
|
||||
var instance = new GameObject();
|
||||
var procCap = instance.AddComponent<ProceduralCapsule>();
|
||||
var handleOverlap = instance.AddComponent<HandleOverlap>();
|
||||
handleOverlap.Parent = root;
|
||||
var collider = instance.AddComponent<CapsuleCollider>();
|
||||
var offset = start - end;
|
||||
var position = start - (offset / 2.0f);
|
||||
var height = offset.magnitude;
|
||||
collider.height = height + (width * 2) * .90f;
|
||||
collider.radius = width * .90f;
|
||||
procCap.height = height + (width);
|
||||
procCap.radius = width;
|
||||
procCap.CreateMesh();
|
||||
|
||||
instance.transform.parent = root.transform;
|
||||
instance.transform.up = offset;
|
||||
if (useWorldSpace)
|
||||
{
|
||||
instance.transform.position = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.transform.position = position + parent.transform.position;
|
||||
instance.transform.rotation = instance.transform.rotation * parent.transform.rotation;
|
||||
}
|
||||
|
||||
// UnityEngine.GameObject.Destroy(handleOverlap);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static GameObject CreateAtPoint(this GameObject parent, Vector3 position, float width,
|
||||
bool useWorldSpace, GameObject root)
|
||||
{
|
||||
var scale = new Vector3(width, width, width);
|
||||
var instance = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
instance.transform.parent = root.transform;
|
||||
instance.transform.localScale = scale * 2;
|
||||
if (useWorldSpace)
|
||||
{
|
||||
instance.transform.position = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.transform.position = position + parent.transform.position;
|
||||
instance.transform.rotation = instance.transform.rotation * parent.transform.rotation;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonHelper.cs.meta
generated
Normal file
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonHelper.cs.meta
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: badf427d082e546a0b2b91e0b251f39d
|
||||
timeCreated: 1515352351
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonJoint.cs
Normal file
18
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonJoint.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.MLAgents
|
||||
{
|
||||
[System.Serializable]
|
||||
public class MarathonJoint
|
||||
{
|
||||
public Joint Joint;
|
||||
public string Name;
|
||||
public string JointName;
|
||||
public Vector2 CtrlRange;
|
||||
public bool? CtrlLimited;
|
||||
public float? Gear;
|
||||
public ConfigurableJoint TrueBase;
|
||||
public Transform TrueTarget;
|
||||
public float MaximumForce;
|
||||
}
|
||||
}
|
||||
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonJoint.cs.meta
generated
Normal file
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonJoint.cs.meta
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e8c7f9aa73bc4a34bb844acd8d4dc68
|
||||
timeCreated: 1515741765
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
12
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSensor.cs
Normal file
12
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSensor.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.MLAgents
|
||||
{
|
||||
[System.Serializable]
|
||||
public class MarathonSensor
|
||||
{
|
||||
public Collider SiteObject;
|
||||
public string Name;
|
||||
public string SiteName;
|
||||
}
|
||||
}
|
||||
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSensor.cs.meta
generated
Normal file
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSensor.cs.meta
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c363fa98af67a4ed8b4ce15b4baefc8d
|
||||
timeCreated: 1520405313
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1270
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSpawner.cs
Normal file
1270
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSpawner.cs
Normal file
File diff suppressed because it is too large
Load Diff
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSpawner.cs.meta
generated
Normal file
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSpawner.cs.meta
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a532e373957504411bd01901bf361ad3
|
||||
timeCreated: 1515299486
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSupport.cs
Normal file
18
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSupport.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class MarathonSupport : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSupport.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/MarathonSupport.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3232628ca9c44e83973a20a6814ed33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
206
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Muscle002.cs
Normal file
206
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Muscle002.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
[System.Serializable]
|
||||
public class Muscle002
|
||||
{
|
||||
public string Name;
|
||||
public BodyHelper002.MuscleGroup Group;
|
||||
|
||||
[Range(-1,1)]
|
||||
public float TargetNormalizedRotationX;
|
||||
[Range(-1,1)]
|
||||
public float TargetNormalizedRotationY;
|
||||
[Range(-1,1)]
|
||||
public float TargetNormalizedRotationZ;
|
||||
public Vector3 MaximumForce;
|
||||
|
||||
public Vector3 ObsLocalPosition;
|
||||
public Quaternion ObsRotation;
|
||||
public Vector3 ObsNormalizedRotation;
|
||||
public Vector3 ObsNormalizedDeltaFromTargetRotation;
|
||||
public Vector3 ObsRotationVelocity;
|
||||
public Vector3 ObsVelocity;
|
||||
|
||||
public Vector3 DebugMaxRotationVelocity;
|
||||
public Vector3 DebugMaxVelocity;
|
||||
|
||||
|
||||
public Quaternion DefaultLocalRotation;
|
||||
public Quaternion ToJointSpaceInverse;
|
||||
public Quaternion ToJointSpaceDefault;
|
||||
|
||||
public Rigidbody Rigidbody;
|
||||
public Transform Transform;
|
||||
public ConfigurableJoint ConfigurableJoint;
|
||||
public Rigidbody Parent;
|
||||
public ConfigurableJoint RootConfigurableJoint;
|
||||
public Quaternion InitialRootRotation;
|
||||
public Vector3 InitialRootPosition;
|
||||
|
||||
Quaternion _lastObsRotation;
|
||||
Vector3 _lastLocalPosition;
|
||||
float _lastUpdateObsTime;
|
||||
bool _firstRunComplete;
|
||||
bool _hasRanVeryFirstInit;
|
||||
|
||||
|
||||
public void UpdateMotor()
|
||||
{
|
||||
float powerMultiplier = 2.5f;
|
||||
var t = ConfigurableJoint.targetAngularVelocity;
|
||||
t.x = TargetNormalizedRotationX * MaximumForce.x;
|
||||
t.y = TargetNormalizedRotationY * MaximumForce.y;
|
||||
t.z = TargetNormalizedRotationZ * MaximumForce.z;
|
||||
ConfigurableJoint.targetAngularVelocity = t;
|
||||
|
||||
var angX = ConfigurableJoint.angularXDrive;
|
||||
angX.positionSpring = 1f;
|
||||
var scale = MaximumForce.x * Mathf.Pow(Mathf.Abs(TargetNormalizedRotationX), 3);
|
||||
angX.positionDamper = Mathf.Max(1f, scale);
|
||||
angX.maximumForce = Mathf.Max(1f, MaximumForce.x * powerMultiplier);
|
||||
ConfigurableJoint.angularXDrive = angX;
|
||||
|
||||
var maxForce = Mathf.Max(MaximumForce.y, MaximumForce.z);
|
||||
var angYZ = ConfigurableJoint.angularYZDrive;
|
||||
angYZ.positionSpring = 1f;
|
||||
var maxAbsRotXY = Mathf.Max(Mathf.Abs(TargetNormalizedRotationY) + Mathf.Abs(TargetNormalizedRotationZ));
|
||||
scale = maxForce * Mathf.Pow(maxAbsRotXY, 3);
|
||||
angYZ.positionDamper = Mathf.Max(1f, scale);
|
||||
angYZ.maximumForce = Mathf.Max(1f, maxForce * powerMultiplier);
|
||||
ConfigurableJoint.angularYZDrive = angYZ;
|
||||
}
|
||||
|
||||
static Vector3 NormalizedEulerAngles(Vector3 eulerAngles)
|
||||
{
|
||||
var x = eulerAngles.x < 180f ?
|
||||
eulerAngles.x :
|
||||
- 360 + eulerAngles.x;
|
||||
var y = eulerAngles.y < 180f ?
|
||||
eulerAngles.y :
|
||||
- 360 + eulerAngles.y;
|
||||
var z = eulerAngles.z < 180f ?
|
||||
eulerAngles.z :
|
||||
- 360 + eulerAngles.z;
|
||||
x = x / 180f;
|
||||
y = y / 180f;
|
||||
z = z / 180f;
|
||||
return new Vector3(x,y,z);
|
||||
}
|
||||
static Vector3 ScaleNormalizedByJoint(Vector3 normalizedRotation, ConfigurableJoint configurableJoint)
|
||||
{
|
||||
var x = normalizedRotation.x > 0f ?
|
||||
(normalizedRotation.x * 180f) / configurableJoint.highAngularXLimit.limit :
|
||||
(-normalizedRotation.x * 180f) / configurableJoint.lowAngularXLimit.limit;
|
||||
var y = (normalizedRotation.y * 180f) / configurableJoint.angularYLimit.limit;
|
||||
var z = (normalizedRotation.z * 180f) / configurableJoint.angularZLimit.limit;
|
||||
var scaledNormalizedRotation = new Vector3(x,y,z);
|
||||
return scaledNormalizedRotation;
|
||||
}
|
||||
|
||||
static Vector3 Vector3Max (Vector3 a, Vector3 b)
|
||||
{
|
||||
var answer = new Vector3(
|
||||
Mathf.Max(Mathf.Abs(a.x), Mathf.Abs(b.x)),
|
||||
Mathf.Max(Mathf.Abs(a.y), Mathf.Abs(b.y)),
|
||||
Mathf.Max(Mathf.Abs(a.z), Mathf.Abs(b.z)));
|
||||
return answer;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
_firstRunComplete = false;
|
||||
Rigidbody.angularVelocity = Vector3.zero;
|
||||
Rigidbody.velocity = Vector3.zero;
|
||||
|
||||
|
||||
if (!_hasRanVeryFirstInit) {
|
||||
Parent = ConfigurableJoint.connectedBody;
|
||||
|
||||
InitialRootRotation = RootConfigurableJoint.transform.rotation;
|
||||
InitialRootPosition = RootConfigurableJoint.transform.position;
|
||||
|
||||
DefaultLocalRotation = LocalRotation;
|
||||
// Vector3 forward = Vector3.Cross (ConfigurableJoint.axis, ConfigurableJoint.secondaryAxis).normalized;
|
||||
//Vector3 up = Vector3.Cross (forward, ConfigurableJoint.axis).normalized;
|
||||
Vector3 forward = this.Transform.forward;
|
||||
Vector3 up = this.Transform.forward;
|
||||
Quaternion toJointSpace = Quaternion.LookRotation(forward, up);
|
||||
|
||||
ToJointSpaceInverse = Quaternion.Inverse(toJointSpace);
|
||||
ToJointSpaceDefault = DefaultLocalRotation * toJointSpace;
|
||||
_hasRanVeryFirstInit = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateObservations()
|
||||
{
|
||||
ObsRotation = this.LocalRotation;
|
||||
ObsRotation = (ToJointSpaceInverse * UnityEngine.Quaternion.Inverse(this.LocalRotation) * this.ToJointSpaceDefault);
|
||||
var r2 = (ToJointSpaceInverse * UnityEngine.Quaternion.Inverse(this.Transform.rotation) * this.ToJointSpaceDefault);
|
||||
var s1 = ScaleNormalizedByJoint(NormalizedEulerAngles((this.LocalRotation * ToJointSpaceDefault).eulerAngles), ConfigurableJoint);
|
||||
var s2 = ScaleNormalizedByJoint(NormalizedEulerAngles((Transform.localRotation * ToJointSpaceDefault).eulerAngles), ConfigurableJoint);
|
||||
var s3 = ScaleNormalizedByJoint(NormalizedEulerAngles((this.LocalRotation * ToJointSpaceInverse).eulerAngles), ConfigurableJoint);
|
||||
var s4 = ScaleNormalizedByJoint(NormalizedEulerAngles((Transform.localRotation * ToJointSpaceInverse).eulerAngles), ConfigurableJoint);
|
||||
var s5 = ScaleNormalizedByJoint(NormalizedEulerAngles((UnityEngine.Quaternion.Inverse(this.LocalRotation) * ToJointSpaceDefault).eulerAngles), ConfigurableJoint);
|
||||
|
||||
var normalizedRotation = NormalizedEulerAngles(ObsRotation.eulerAngles);
|
||||
// var normalizedRotation = NormalizedEulerAngles(this.LocalRotation.eulerAngles);
|
||||
ObsNormalizedRotation = ScaleNormalizedByJoint(normalizedRotation, ConfigurableJoint);
|
||||
ObsNormalizedDeltaFromTargetRotation =
|
||||
new Vector3(TargetNormalizedRotationX, TargetNormalizedRotationY, TargetNormalizedRotationZ) - ObsNormalizedRotation;
|
||||
|
||||
// Debug code
|
||||
// if (Group == BodyHelper002.MuscleGroup.Head){
|
||||
// var debug = 1;
|
||||
// }
|
||||
|
||||
if (_firstRunComplete == false){
|
||||
_lastUpdateObsTime = Time.time;
|
||||
_lastObsRotation = ObsRotation;
|
||||
_lastLocalPosition = Transform.localPosition;
|
||||
}
|
||||
var dt = Time.time - _lastUpdateObsTime;
|
||||
_lastUpdateObsTime = Time.time;
|
||||
var rotationVelocity = ObsRotation.eulerAngles - _lastObsRotation.eulerAngles;
|
||||
rotationVelocity = NormalizedEulerAngles(rotationVelocity);
|
||||
rotationVelocity /= 128f;
|
||||
if (dt > 0f)
|
||||
rotationVelocity /= dt;
|
||||
ObsRotationVelocity = rotationVelocity;
|
||||
_lastObsRotation = ObsRotation;
|
||||
var rootBone = RootConfigurableJoint.transform;
|
||||
var toRootSpace = Quaternion.Inverse(RootConfigurableJoint.transform.rotation) * rootBone.rotation;
|
||||
Quaternion rootRotation = Quaternion.Inverse(rootBone.rotation * toRootSpace) * Transform.rotation;
|
||||
ObsLocalPosition = Transform.position - RootConfigurableJoint.transform.position;
|
||||
var velocity = ObsLocalPosition - _lastLocalPosition;
|
||||
ObsVelocity = velocity;
|
||||
if (dt > 0f)
|
||||
velocity /= dt;
|
||||
_lastLocalPosition = ObsLocalPosition;
|
||||
|
||||
DebugMaxRotationVelocity = Vector3Max(DebugMaxRotationVelocity, rotationVelocity);
|
||||
DebugMaxVelocity = Vector3Max(DebugMaxVelocity, velocity);
|
||||
|
||||
_firstRunComplete = true;
|
||||
}
|
||||
public Quaternion LocalRotation {
|
||||
get {
|
||||
// around root Rotation
|
||||
return Quaternion.Inverse(RootRotation) * Transform.rotation;
|
||||
|
||||
// around parent space
|
||||
// return Quaternion.Inverse(ParentRotation) * transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public Quaternion RootRotation {
|
||||
get {
|
||||
return InitialRootRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Muscle002.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Muscle002.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1a3e067c0e5a43b79cb9649ee706f12
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
158
Assets/3_MarathonEnvs/Scripts/Ragdoll002/ProceduralCapsule.cs
Normal file
158
Assets/3_MarathonEnvs/Scripts/Ragdoll002/ProceduralCapsule.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
//------------------------------//
|
||||
// ProceduralCapsule.cs //
|
||||
// Written by Jay Kay //
|
||||
// 2016/05/27 //
|
||||
//------------------------------//
|
||||
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
|
||||
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
|
||||
public class ProceduralCapsule : MonoBehaviour
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[ContextMenu("Generate Procedural Capsule")]
|
||||
public void GenerateProceduralCapsule()
|
||||
{
|
||||
// GenerateMesh();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
public float height = 2f;
|
||||
public float radius = 0.5f;
|
||||
|
||||
public int segments = 24;
|
||||
|
||||
|
||||
// void GenerateMesh()
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
|
||||
public void CreateMesh()
|
||||
{
|
||||
// make segments an even number
|
||||
if (segments % 2 != 0)
|
||||
segments++;
|
||||
|
||||
// extra vertex on the seam
|
||||
int points = segments + 1;
|
||||
|
||||
// calculate points around a circle
|
||||
float[] pX = new float[points];
|
||||
float[] pZ = new float[points];
|
||||
float[] pY = new float[points];
|
||||
float[] pR = new float[points];
|
||||
|
||||
float calcH = 0f;
|
||||
float calcV = 0f;
|
||||
|
||||
for (int i = 0; i < points; i++)
|
||||
{
|
||||
pX[i] = Mathf.Sin(calcH * Mathf.Deg2Rad);
|
||||
pZ[i] = Mathf.Cos(calcH * Mathf.Deg2Rad);
|
||||
pY[i] = Mathf.Cos(calcV * Mathf.Deg2Rad);
|
||||
pR[i] = Mathf.Sin(calcV * Mathf.Deg2Rad);
|
||||
|
||||
calcH += 360f / (float) segments;
|
||||
calcV += 180f / (float) segments;
|
||||
}
|
||||
|
||||
|
||||
// - Vertices and UVs -
|
||||
|
||||
Vector3[] vertices = new Vector3[points * (points + 1)];
|
||||
Vector2[] uvs = new Vector2[vertices.Length];
|
||||
int ind = 0;
|
||||
|
||||
// Y-offset is half the height minus the diameter
|
||||
// float yOff = ( height - ( radius * 2f ) ) * 0.5f;
|
||||
float yOff = (height - (radius)) * 0.5f;
|
||||
if (yOff < 0)
|
||||
yOff = 0;
|
||||
|
||||
// uv calculations
|
||||
float stepX = 1f / ((float) (points - 1));
|
||||
float uvX, uvY;
|
||||
|
||||
// Top Hemisphere
|
||||
int top = Mathf.CeilToInt((float) points * 0.5f);
|
||||
|
||||
for (int y = 0; y < top; y++)
|
||||
{
|
||||
for (int x = 0; x < points; x++)
|
||||
{
|
||||
vertices[ind] = new Vector3(pX[x] * pR[y], pY[y], pZ[x] * pR[y]) * radius;
|
||||
vertices[ind].y = yOff + vertices[ind].y;
|
||||
|
||||
uvX = 1f - (stepX * (float) x);
|
||||
uvY = (vertices[ind].y + (height * 0.5f)) / height;
|
||||
uvs[ind] = new Vector2(uvX, uvY);
|
||||
|
||||
ind++;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom Hemisphere
|
||||
int btm = Mathf.FloorToInt((float) points * 0.5f);
|
||||
|
||||
for (int y = btm; y < points; y++)
|
||||
{
|
||||
for (int x = 0; x < points; x++)
|
||||
{
|
||||
vertices[ind] = new Vector3(pX[x] * pR[y], pY[y], pZ[x] * pR[y]) * radius;
|
||||
vertices[ind].y = -yOff + vertices[ind].y;
|
||||
|
||||
uvX = 1f - (stepX * (float) x);
|
||||
uvY = (vertices[ind].y + (height * 0.5f)) / height;
|
||||
uvs[ind] = new Vector2(uvX, uvY);
|
||||
|
||||
ind++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// - Triangles -
|
||||
|
||||
int[] triangles = new int[(segments * (segments + 1) * 2 * 3)];
|
||||
|
||||
for (int y = 0, t = 0; y < segments + 1; y++)
|
||||
{
|
||||
for (int x = 0; x < segments; x++, t += 6)
|
||||
{
|
||||
triangles[t + 0] = ((y + 0) * (segments + 1)) + x + 0;
|
||||
triangles[t + 1] = ((y + 1) * (segments + 1)) + x + 0;
|
||||
triangles[t + 2] = ((y + 1) * (segments + 1)) + x + 1;
|
||||
|
||||
triangles[t + 3] = ((y + 0) * (segments + 1)) + x + 1;
|
||||
triangles[t + 4] = ((y + 0) * (segments + 1)) + x + 0;
|
||||
triangles[t + 5] = ((y + 1) * (segments + 1)) + x + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// - Assign Mesh -
|
||||
|
||||
MeshFilter mf = gameObject.GetComponent<MeshFilter>();
|
||||
Mesh mesh = mf.sharedMesh;
|
||||
if (!mesh)
|
||||
{
|
||||
mesh = new Mesh();
|
||||
mf.sharedMesh = mesh;
|
||||
}
|
||||
|
||||
mesh.Clear();
|
||||
|
||||
mesh.name = "ProceduralCapsule";
|
||||
|
||||
mesh.vertices = vertices;
|
||||
mesh.uv = uvs;
|
||||
mesh.triangles = triangles;
|
||||
|
||||
mesh.RecalculateBounds();
|
||||
mesh.RecalculateNormals();
|
||||
}
|
||||
}
|
||||
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/ProceduralCapsule.cs.meta
generated
Normal file
13
Assets/3_MarathonEnvs/Scripts/Ragdoll002/ProceduralCapsule.cs.meta
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d940240a49514996a7c1face83becce
|
||||
timeCreated: 1515310310
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
66
Assets/3_MarathonEnvs/Scripts/Ragdoll002/RagDoll002.cs
Normal file
66
Assets/3_MarathonEnvs/Scripts/Ragdoll002/RagDoll002.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class RagDoll002 : MonoBehaviour {
|
||||
|
||||
[System.Serializable]
|
||||
public class MusclePower
|
||||
{
|
||||
public string Muscle;
|
||||
public Vector3 PowerVector;
|
||||
}
|
||||
|
||||
public List<MusclePower> MusclePowers;
|
||||
|
||||
public float MotorScale = 1f;
|
||||
|
||||
// Use this for initialization
|
||||
void Start () {
|
||||
Setup();
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update () {
|
||||
|
||||
}
|
||||
|
||||
void Setup () {
|
||||
// handle collision overlaps
|
||||
IgnoreCollision("torso", new []{"left_upper_arm", "right_upper_arm"});
|
||||
IgnoreCollision("butt", new []{"left_thigh", "right_thigh"});
|
||||
|
||||
IgnoreCollision("left_larm", new []{"left_upper_arm"});
|
||||
IgnoreCollision("right_larm", new []{"right_upper_arm"});
|
||||
IgnoreCollision("left_shin", new []{"left_thigh"});
|
||||
IgnoreCollision("right_shin", new []{"right_thigh"});
|
||||
|
||||
IgnoreCollision("right_shin", new []{"right_right_foot"});
|
||||
IgnoreCollision("left_shin", new []{"left_left_foot"});
|
||||
|
||||
|
||||
//
|
||||
var joints = GetComponentsInChildren<Joint>().ToList();
|
||||
foreach (var joint in joints)
|
||||
joint.enablePreprocessing = false;
|
||||
}
|
||||
void IgnoreCollision(string first, string[] seconds)
|
||||
{
|
||||
foreach (var second in seconds)
|
||||
{
|
||||
IgnoreCollision(first, second);
|
||||
}
|
||||
}
|
||||
void IgnoreCollision(string first, string second)
|
||||
{
|
||||
var rigidbodies = GetComponentsInChildren<Rigidbody>().ToList();
|
||||
var colliderOnes = rigidbodies.FirstOrDefault(x=>x.name.Contains(first))?.GetComponents<Collider>();
|
||||
var colliderTwos = rigidbodies.FirstOrDefault(x=>x.name.Contains(second))?.GetComponents<Collider>();
|
||||
if (colliderOnes == null || colliderTwos == null)
|
||||
return;
|
||||
foreach (var c1 in colliderOnes)
|
||||
foreach (var c2 in colliderTwos)
|
||||
Physics.IgnoreCollision(c1, c2);
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/RagDoll002.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/RagDoll002.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e788eb6109bb04aa1bd822f9fc98f4bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class ScoreHistogramData
|
||||
{
|
||||
int _columnCount;
|
||||
int _historyDepth;
|
||||
|
||||
public List<List<double>> Items;
|
||||
|
||||
|
||||
public ScoreHistogramData (int columnCount, int historyDepth)
|
||||
{
|
||||
_columnCount = columnCount;
|
||||
_historyDepth = historyDepth;
|
||||
ReCreateScoreHistogramData();
|
||||
}
|
||||
|
||||
public void ReCreateScoreHistogramData ()
|
||||
{
|
||||
Items = new List<List<double>>();
|
||||
for (int i = 0; i < _columnCount; i++)
|
||||
{
|
||||
// var item = Enumerable.Range(0,_historyDepth).Select(x=>default(T)).ToList();
|
||||
var item = new List<double>();
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
public void SetItem(int column, double value)
|
||||
{
|
||||
Items[column].Add(value);
|
||||
if (Items[column].Count > _historyDepth)
|
||||
Items[column].RemoveAt(0);
|
||||
}
|
||||
public double GetAverage(int column)
|
||||
{
|
||||
double average = Items[column].Average();
|
||||
return average;
|
||||
}
|
||||
public List<double> GetAverages()
|
||||
{
|
||||
List<double> averages = Items.Select(x=>x.Count > 0 ? x.Average() : 0).ToList();
|
||||
return averages;
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/ScoreHistogramData.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/ScoreHistogramData.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5026d714f0bd4ce6bed38f75bd48588
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Scorer.cs
Normal file
50
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Scorer.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.MLAgents;
|
||||
using UnityEngine;
|
||||
using ManyWorlds;
|
||||
|
||||
public class Scorer : MonoBehaviour
|
||||
{
|
||||
public int TotalEpisodesToScore = 100;
|
||||
public int EpisodesScored;
|
||||
|
||||
public float AverageScore;
|
||||
public float StdDiv;
|
||||
public string ScoreInfo;
|
||||
public List<float> scores;
|
||||
public List<string> scoreInfos;
|
||||
[TextArea]
|
||||
public string ScoreReport;
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
scores = new List<float>(TotalEpisodesToScore);
|
||||
scoreInfos = new List<string>(TotalEpisodesToScore);
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void ReportScore(float score, string scoreInfo)
|
||||
{
|
||||
if (EpisodesScored >= TotalEpisodesToScore)
|
||||
return;
|
||||
scores.Add(score);
|
||||
scoreInfos.Add(scoreInfo);
|
||||
ScoreInfo = scoreInfo;
|
||||
EpisodesScored = scores.Count;
|
||||
AverageScore = scores.Average();
|
||||
var sum = scores.Sum(d => (d - AverageScore) * (d - AverageScore));
|
||||
StdDiv = Mathf.Sqrt(sum / EpisodesScored);
|
||||
string name = string.Empty;
|
||||
var spawnEnv = FindObjectOfType<SpawnableEnv>();
|
||||
if (spawnEnv != null)
|
||||
name = $"{spawnEnv.gameObject.name} ";
|
||||
ScoreReport = $"{name}AveScore:{AverageScore}, StdDiv:{StdDiv} over {EpisodesScored} episodes using {scoreInfo}";
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Scorer.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/Scorer.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92cb079fa72494f018e7d3ebcc3851c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.MLAgents
|
||||
{
|
||||
public class SendOnCollisionTrigger : MonoBehaviour
|
||||
{
|
||||
void OnCollisionEnter(Collision other)
|
||||
{
|
||||
// Messenger.
|
||||
var otherGameobject = other.gameObject;
|
||||
var marathonAgent = otherGameobject.GetComponentInParent<MarathonAgent>();
|
||||
if (marathonAgent != null)
|
||||
marathonAgent.OnTerrainCollision(otherGameobject, this.gameObject);
|
||||
var iOnTerrainCollision = otherGameobject.GetComponentInParent<IOnTerrainCollision>();
|
||||
if (iOnTerrainCollision != null)
|
||||
iOnTerrainCollision.OnTerrainCollision(otherGameobject, this.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/SendOnCollisionTrigger.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/SendOnCollisionTrigger.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68231b96042614ce992397230a3bb5fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/3_MarathonEnvs/Scripts/Ragdoll002/TerrainSetup.cs
Normal file
20
Assets/3_MarathonEnvs/Scripts/Ragdoll002/TerrainSetup.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class TerrainSetup : MonoBehaviour
|
||||
{
|
||||
public Vector3 TerrainSize;
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
var terrain = GetComponent<Terrain>();
|
||||
terrain.terrainData.size = TerrainSize;
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/TerrainSetup.cs.meta
generated
Normal file
11
Assets/3_MarathonEnvs/Scripts/Ragdoll002/TerrainSetup.cs.meta
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35dfb25d03f7744bc924a3a045e3c639
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user