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;
}
}
}
}