using System.Collections.Generic; using UnityEngine; namespace CreatureControl { /// /// A leg of a creature /// [System.Serializable] public class Leg { [SerializeField] private Transform _coxa; /// /// The hip bone /// public Transform coxa { get => this._coxa; set { this._coxa = value; this._femurLength = 0; } } [SerializeField] private Transform _femur; /// /// The upper leg or thigh bone /// public Transform femur { get => this._femur; set { this._femur = value; this._femurLength = 0; } } [SerializeField] private Transform _tibia; /// /// The lower leg or shank bone /// public Transform tibia { get => this._tibia; set { this._tibia = value; this._femurLength = 0; this._tibiaLength = 0; } } [SerializeField] private Transform _tarsus; /// /// The foot bone /// public Transform tarsus { get => this._tarsus; set { this._tarsus = value; this._tibiaLength = 0; this._tarsusLength = 0; } } [SerializeField] private Transform _phalanges; /// /// The toes /// public Transform phalanges { get => this._phalanges; set { this._phalanges = value; this._tarsusLength = 0; this._phalangesLength = 0; } } [SerializeField] private Transform _end; /// /// The end of the leg /// public Transform end { get => this._end; set { this._end = value; this._phalangesLength = 0; } } #region Bones [SerializeField] private float _femurLength; /// /// The length of the femur bone /// public float femurLength { get { if (_femurLength <= 0 && this.femur != null && this.tibia != null) _femurLength = Vector3.Distance(this.femur.position, this.tibia.position); return _femurLength; } } [SerializeField] private float _tibiaLength; /// /// The length of the tibia bone /// public float tibiaLength { get { if (_tibiaLength <= 0 && this.tibia != null && this.tarsus != null) _tibiaLength = Vector3.Distance(this.tibia.position, this.tarsus.position); return _tibiaLength; } } [SerializeField] private float _tarsusLength; /// /// The length of the tarsus bone /// public float tarsusLength { get { if (_tarsusLength <= 0 && this.tarsus != null && this.phalanges != null) _tarsusLength = Vector3.Distance(this.tarsus.position, this.phalanges.position); return _tarsusLength; } } [SerializeField] private float _phalangesLength; /// /// The length of the phalanges /// public float phalangesLength { get { if (_phalangesLength <= 0 && this.phalanges != null && this.end != null) _phalangesLength = Vector3.Distance(this.phalanges.position, this.end.position); return _phalangesLength; } } /// /// The length of the leg from hip to foot /// /// This consists of the femur and tibia public float length => femurLength + tibiaLength; /// /// The size of the foot /// /// This consists of the tarsus and phalanges public float footLength => tarsusLength + phalangesLength; public void ResetLengths() { _femurLength = 0; _tibiaLength = 0; _tarsusLength = 0; _phalangesLength = 0; } /// /// Try to determine the leg bones /// /// the first bone of the leg (could be coxa or femur depending on chain length) public void DetectBones(Transform root) { if (root == null) return; coxa = femur = tibia = tarsus = phalanges = end = null; // gather a straight chain following single-child links List chain = new(); Transform current = root; chain.Add(current); while (current.childCount == 1) { current = current.GetChild(0); chain.Add(current); } // the detected end bone is the last element in the collected chain end = chain[^1]; // map chain length to bone roles according to rules: // 1 => femur // 2 => femur, tibia // 3 => femur, tibia, tarsus // 4 => femur, tibia, tarsus, phalanges // 5+ => coxa, femur, tibia, tarsus, phalanges (first 5) int count = chain.Count; if (count == 1) femur = chain[0]; else if (count == 2) { femur = chain[0]; tibia = chain[1]; } else if (count == 3) { femur = chain[0]; tibia = chain[1]; tarsus = chain[2]; } else if (count == 4) { femur = chain[0]; tibia = chain[1]; tarsus = chain[2]; phalanges = chain[3]; } else // count >= 5 { coxa = chain[0]; femur = chain[1]; tibia = chain[2]; tarsus = chain[3]; phalanges = chain[4]; } } #endregion Bones /// /// Check if all bones of the legs have been configured /// /// True when all bones are configured public bool isConfigured => this.femur != null && this.tibia != null && this.tarsus != null && this.phalanges != null && this.end != null; public Side side; public bool isLeft => side == Side.Left; public bool isRight => side == Side.Right; private Vector3 _kneeAxis = Vector3.zero; public Vector3 kneeAxis { get { if (this._kneeAxis.sqrMagnitude == 0) { Vector3 femurWorldDirection = (this.tibia.position - this.femur.position).normalized; Vector3 tibiaWorldDirection = (this.tarsus.position - this.tibia.position).normalized; Vector3 kneeWorldAxis = Vector3.Cross(femurWorldDirection, tibiaWorldDirection); Vector3 kneeLocalAxis = this.femur.InverseTransformDirection(kneeWorldAxis); this._kneeAxis = kneeLocalAxis; } return this._kneeAxis; } } } }