using System;
using System.Linq;
using UnityEngine;
using Panzerwehr.Physics.Walker;
using Panzerwehr.Physics;
namespace Panzerwehr.Game.WalkCalculators {
[Serializable]
public class DebugParametricJaegerIVWalkCalculator : MonoBehaviour, IPhysicsWalkerVarProvider {
#pragma warning disable 0649
[SerializeField] [Range(0f, 2f)] float velocityDrag;
[SerializeField] [Range(0f, 2f)] float torsionDrag;
[Header("Suspension Behaviour")]
[SerializeField] [Range(1f, 5f)] float maxSuspensionLift;
[SerializeField] [Range(0.1f, 10f)] float legAxisAdjust;
[SerializeField] [Range(0.1f, 10f)] float legPlaneHeightAdjust;
[Header("Leg span and equilibrium")]
[SerializeField] float legSpanRadius;
[SerializeField] [Range(0.3f, 1f)] float legSpanEllipse;
[SerializeField] [Range(0f, 10f)] float fullLegSpanAtThisVelocity;
[SerializeField] Vector2 jointEquilibriumPoint;
[Header("Foot collision")]
[SerializeField] LayerMask legCollisionLayers;
[SerializeField] [Range(0f, 1f)] float contactKeepDistance;
[Header("Adaptive equilibrium")]
[SerializeField] [Range(0f, 0.5f)] float limpEquilibriumOffset; // return different equilibrium
[SerializeField] [Range(0.6f, 1f)] float limpRadiusMultiplier;
[SerializeField] AnimationCurve dynamicEquilibriumX;
[SerializeField] Vector2 affectEquilibriumMovingForward;
[Header("Leg parameters")]
[SerializeField] float individualLegTorsionTolerance;
[SerializeField] float averageLegTorsionTolerance;
#pragma warning restore 0649
void Awake() {
AttachTo(GetComponent<PhysicsWalker>());
}
public void AttachTo(PhysicsWalker walker) {
if (walker == null) return;
Walker = walker;
Walker.Vars = this;
}
public PhysicsWalker Walker { get; set; }
public float LegSpanRadius => legSpanRadius * ((Walker.CountRetractedLegs == 1) ? limpRadiusMultiplier : 1f);
public LayerMask LegCollisionLayers => legCollisionLayers;
public float VelocityDrag => velocityDrag;
public float TorsionDrag => torsionDrag;
public float MaxSuspensionLift => maxSuspensionLift;
public float LegAxisAdjust => legAxisAdjust;
public float LegPlaneHeightAdjust => legPlaneHeightAdjust;
public float LegSpanEllipse => legSpanEllipse;
public float ContactKeepThreshold => contactKeepDistance;
public float FullLegSpanAtThisVelocity => fullLegSpanAtThisVelocity;
public float IndividualLegTorsionTolerance => individualLegTorsionTolerance;
public float AverageLegTorsionTolerance => averageLegTorsionTolerance;
public Vector2 GetEquilibrium(LegIdentities legID) {
if (Walker.CountRetractedLegs == 1) return GetLimpingEquilibrium(legID);
var eq = GetNominalEquilibrium(legID);
var longitudinaliness = RelativeVelocity.normalized.y.Map(0.8f, 1f, 0f, 1f, true);
longitudinaliness *= Mathf.Clamp01(RelativeVelocity.magnitude * 0.2f);
if (longitudinaliness > float.Epsilon) {
eq.x += Mathf.Sign(eq.x) * affectEquilibriumMovingForward.x * longitudinaliness * (1f - AwayFromOptimumHeightFactor);
eq.y += Mathf.Sign(eq.y) * affectEquilibriumMovingForward.y * longitudinaliness * (1f - AwayFromOptimumHeightFactor);
}
eq.x += Mathf.Sign(eq.x) * dynamicEquilibriumX.Evaluate(Walker.DesiredStand);
return eq;
}
const float OptimalStand = 2f;
public float Strain => AboveOptimumHeightFactor * 1.5f + RelativeVelocity.magnitude * 0.14f;
public float AwayFromOptimumHeightFactor => Mathf.Clamp01(Mathf.Abs(Walker.DesiredStand - OptimalStand) * 0.6f);
public float AboveOptimumHeightFactor => Mathf.Clamp01(Walker.DesiredStand - OptimalStand) * 0.6f;
Vector2 RelativeVelocity => Vector3.Scale(Walker.SuspendedRigidbody.transform.InverseTransformVector(Walker.CurrentWalkVelocity), new Vector3(1f, 0f, 1f)).Flatten();
private Vector2 GetNominalEquilibrium(LegIdentities id) {
return Vector2.Scale(jointEquilibriumPoint, id.DirectionMultiplier());
}
private Vector2 GetLimpingEquilibrium(LegIdentities legID) {
var retracted = Walker.Legs.FirstOrDefault(leg => leg.Retracted);
var offset = Vector2.zero;
if (retracted != null)
offset = Vector2.Scale(retracted.identity.DirectionMultiplier(), GetNominalEquilibrium(legID)) * limpEquilibriumOffset;
return GetNominalEquilibrium(legID) + offset;
}
}
}
namespace Panzerwehr.API {
public class MechBuilderAPI: IAPI {
#region Fields and lookups
// A general purpose cache that is persistent across a single build session and whose objects can be filled
// and accessed by string IDs. Use to store relevant transforms, gameobjects, floats, strings, whatever.
Dictionary<string, object> cache = new Dictionary<string, object>();
// Component classes are meant to be extensible - let's say you distribute a Submarine DLC and it comes with
// a new MechComponent in a dll called CompressionTank, that you want associated with "compression_tank"
Dictionary<string, Type> componentClassLookup = new Dictionary<string, Type>();
// When asked to attach a script, these are the assemblies the mech builder will search in:
List<System.Reflection.Assembly> lookupAssemblies = new List<System.Reflection.Assembly>();
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
#endregion
#region Handy accessors for current build session
public IMech Mech { get; private set; }
public Hangar.Model.Blueprint Blueprint { get; private set; }
#endregion
#region Events
public event Action<IMech> BuildFinished;
#endregion
#region Build Session management
void ClearBuildSession() {
cache.Clear();
Mech = null;
}
public void StartBuildSession(Hangar.Model.Blueprint blueprint) {
sw.Reset();
sw.Start();
ClearBuildSession();
this.Blueprint = blueprint;
}
public void FinalizeBuildSession() {
if (Mech == null) throw new InvalidOperationException("Mech not created; cannot finish construction");
Mech.FinalizeConstruction();
sw.Stop();
BuildFinished?.Invoke(this.Mech);
Debug.Log("MECH BUILT IN " + sw.Elapsed.TotalMilliseconds.ToString("0.00") + "ms");
// ClearBuildSession();
}
#endregion
#region Registrations
public void RegisterAssemblies(IEnumerable<System.Reflection.Assembly> assemblies) {
lookupAssemblies.AddRange(assemblies);
lookupAssemblies = lookupAssemblies.Distinct().ToList();
}
public void RegisterComponentClass(string typeID, Type componentClass) {
VerifyComponentTypeDeclarationIsValid(typeID, componentClass);
componentClassLookup.Add(typeID, componentClass);
}
#endregion
#region Various useful getters
public void Cache(string id, object @object) => cache[id] = @object;
public object Cached(string id) { cache.TryGetValue(id, out var value); return value; }
public GameObject GetCameraObject() {
return Camera.main.gameObject;
}
public VRInput.VRConfiguration GetVRConfiguration() => GameObject.FindObjectOfType<VRInput.VRConfiguration>();
public Transform GetVRRig() => GetVRConfiguration().TrackingRoot;
public Type GetTypeByName(string typename) {
return UnityUtils.FindTypeByTypeName(lookupAssemblies, typename);
}
#endregion
#region Object creation utilities
public GameObject SpawnObject(string path, GameObject target = null) {
var prefab = Resources.Load<GameObject>(path);
if (prefab == null) throw new InvalidOperationException($"Path `{path}` could not be instantiated");
var result = UnityEngine.Object.Instantiate(prefab);
if (target != null) result.transform.SetParent(target.transform, false);
return result;
}
public GameObject CreateBlankObject(string name, GameObject target = null) {
var go = new GameObject();
go.name = name;
if (target != null) go.transform.SetParent(target.transform, false);
return go;
}
public GameObject Wrap(string name, GameObject target) {
if (target == null)
throw new InvalidOperationException($"Wrap target cannot be null");
var parent = target.transform.parent;
var go = new GameObject(name);
go.transform.parent = parent;
go.transform.localPosition = target.transform.localPosition;
go.transform.localScale = target.transform.localScale;
go.transform.localRotation = target.transform.localRotation;
target.transform.parent = go.transform;
return go;
}
public GameObject FindOrCreateChild(GameObject source, string relativePath) {
var subpaths = relativePath.Split('/');
var currentObject = source;
foreach (var subpath in subpaths) {
var child = currentObject.transform.Find(subpath);
currentObject = child?.gameObject ?? CreateBlankObject(subpath, currentObject);
}
return currentObject;
}
public void CreateMechObject(GameObject target) {
this.Mech = target.AddComponent<Mech.Mech>();
this.Mech.OnCreated();
}
#endregion
#region Various useful methods
public IMechComponent InstallComponent(GameObject target, string moduleID) {
var module = APIs.HangarAPI.Defs.GetModule(moduleID);
if (module == null) Debug.LogError($"Null module: {moduleID}");
var type = GetMechComponentFromModuleType(module.Type);
if (type == null) {
Debug.LogWarning($"Cannot find component type for {module.Type}; will not create mech component");
return null;
}
var component = target.AddComponent(type) as IMechComponent;
Mech.InstallComponent(component, module);
return component;
}
public Rigidbody AttachRigidbody(GameObject @object, float mass) {
if (@object.GetComponent<Rigidbody>() != null)
throw new ArgumentException($"Object {@object.name} already has a rigidbody");
var rb = @object.AddComponent<Rigidbody>();
rb.mass = mass;
return rb;
}
public void ReparentAtIdentity(GameObject child, GameObject parent) {
child.transform.parent = parent.transform;
child.transform.localPosition = Vector3.zero;
child.transform.localRotation = Quaternion.identity;
child.transform.localScale = Vector3.one;
}
public Component AttachScript(GameObject target, string typename) {
if (target == null) Debug.LogError($"Trying to attach {typename} script to [null]");
var type = GetTypeByName(typename);
if (type == null) throw new ArgumentException($"Type {typename} not found by name!");
var mb = target.AddComponent(type) as MonoBehaviour;
return mb;
}
public void SetSlotRoot(string id, GameObject root) {
Cache($"SLOT[{id}]", root);
}
public GameObject GetSlotRoot(string id) {
var result = Cached($"SLOT[{id}]") as GameObject;
if (result == null) throw new InvalidOperationException($"Cannot get slot {id}");
return result;
}
public void Broadcast(string message, GameObject @object, object param = null) {
@object.BroadcastMessage(message, param, SendMessageOptions.DontRequireReceiver);
}
#endregion
#region Local / private utilities
private Type GetMechComponentFromModuleType(string typeID) {
componentClassLookup.TryGetValue(typeID, out var type);
return type;
}
private void VerifyComponentTypeDeclarationIsValid(string typeID, Type componentClass) {
Debug.Assert(componentClass != null, $"Trying to declare null as a component type: {typeID}");
Debug.Assert(
typeof(IMechComponent).IsAssignableFrom(componentClass),
$"Cannot declare mech component {componentClass.Name} as '{typeID}' since it does not implement {nameof(IMechComponent)}"
);
Debug.Assert(
!componentClassLookup.ContainsKey(typeID),
$"Duplicate {typeID} component type assignation: {componentClass.Name } cannot be registered!"
);
}
#endregion
}
}