diff --git a/CreatureControl/Editor/Scripts/Creature_Editor.cs b/CreatureControl/Editor/Scripts/Creature_Editor.cs index e78a8df..e60705c 100644 --- a/CreatureControl/Editor/Scripts/Creature_Editor.cs +++ b/CreatureControl/Editor/Scripts/Creature_Editor.cs @@ -1,51 +1,54 @@ using UnityEditor; using UnityEditor.SceneManagement; -using UnityEngine; - namespace Passer.CreatureControl { [CustomEditor(typeof(Creature), true)] public class Creature_Editor : Editor { + /// + /// The creature managed by this editor + /// protected Creature creature; - #region Init + #region Start + /// + /// Enable the creature editor + /// public virtual void OnEnable() { - creature = target as Creature; + this.creature = target as Creature; + // Keep track if anything changed while enabling the creature editor bool anythingChanged = false; - if (!IsPrefab(creature)) { - anythingChanged |= creature.CheckTargetRig(); - anythingChanged |= creature.CheckModel(); + if (IsPrefab(this.creature) == false) { + // Only do this when it is not a prefab + anythingChanged |= this.creature.CheckTargetRig(); + anythingChanged |= this.creature.CheckModel(); } - creature.targetRig.MatchTo(creature, ref anythingChanged); + this.creature.targetRig.MatchTo(this.creature, ref anythingChanged); + // As the above functions do not use the serialized object + // We need to manually persist the changes. if (anythingChanged) { - EditorUtility.SetDirty(creature); + EditorUtility.SetDirty(this.creature); AssetDatabase.SaveAssets(); } } + /// + /// Check if the given creature is a prefab + /// + /// The creature to check + /// True when it is a prefab public static bool IsPrefab(Creature creature) { PrefabStage prefabStage = PrefabStageUtility.GetPrefabStage(creature.gameObject); return prefabStage != null; } - #endregion Init + #endregion Start - #region Scene - - public virtual void OnSceneGUI() { - if (Application.isPlaying || creature.enabled == false) - return; - - creature.UpdatePose(); - } - - #endregion Scene } } \ No newline at end of file diff --git a/CreatureControl/Runtime/Scripts/Creature.cs b/CreatureControl/Runtime/Scripts/Creature.cs index fb128cd..12922f3 100644 --- a/CreatureControl/Runtime/Scripts/Creature.cs +++ b/CreatureControl/Runtime/Scripts/Creature.cs @@ -3,15 +3,24 @@ using UnityEngine; namespace Passer.CreatureControl { public class Creature : MonoBehaviour { + /// + /// The (hopefully rigged) 3D model of the creature + /// + public Transform model; + /// The target bones rig /// The target bones rig contain the target pose of the creature /// The creature movements will try to move the creature such that the target pose is reached /// as closely as possible - public Transform model; - public TargetRig targetRig; + /// + /// The positional different between the target rig and model root + /// public Vector3 targetToModelTranslation; + /// + /// The rotational difference between the target rig and the model root + /// public Quaternion targetToModelRotation; #region Init @@ -20,27 +29,32 @@ namespace Passer.CreatureControl { /// Ensure a target rig is available /// /// The name of the target rig resource - /// True when the creature rig has been updated + /// True when the target rig has been updated /// The parameter is used to instantiate a new target rig when none has been found. public bool CheckTargetRig(string targetRigResourceName) { - if (this.targetRig == null) { - // See if there is a target rig, but we just haven't found it - this.targetRig = this.GetComponentInChildren(); - if (this.targetRig == null) { - GameObject targetsRigPrefab = Resources.Load(targetRigResourceName); - GameObject targetRig = Instantiate(targetsRigPrefab); - targetRig.name = "Target Rig"; - - targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation); - targetRig.transform.SetParent(this.transform); - this.targetRig = targetRig.GetComponent(); - } - return true; - } - else + if (this.targetRig != null) return false; + + // See if there is a target rig, but we just haven't found it + this.targetRig = this.GetComponentInChildren(); + if (this.targetRig == null) { + // Nope, there is no target rig, so instantiate it using the given resource name + GameObject targetsRigPrefab = Resources.Load(targetRigResourceName); + GameObject targetRig = Instantiate(targetsRigPrefab); + targetRig.name = "Target Rig"; + + targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation); + targetRig.transform.SetParent(this.transform); + this.targetRig = targetRig.GetComponent(); + } + return true; } + /// + /// Ensure a target rig is available + /// + /// True when the target rig has been updated + /// This tries to instantiate the default target rig resource public virtual bool CheckTargetRig() { return CheckTargetRig("TargetRig"); } @@ -50,25 +64,30 @@ namespace Passer.CreatureControl { /// /// True when the creature rig has been updated public bool CheckModel() { - if (this.model == null) { - SkinnedMeshRenderer[] skinnedMeshRenderers = this.GetComponentsInChildren(); - foreach (SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers) { - Transform rendererParent = skinnedMeshRenderer.transform.parent; - if (this.model == null || this.model == rendererParent) - this.model = rendererParent; - else - Debug.LogWarning("Unclear model root"); - } - return this.model != null; - } - else + if (this.model != null) return false; + + // We determine the model root as the parent of the renderers + SkinnedMeshRenderer[] skinnedMeshRenderers = this.GetComponentsInChildren(); + foreach (SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers) { + Transform rendererParent = skinnedMeshRenderer.transform.parent; + if (this.model == null || this.model == rendererParent) + this.model = rendererParent; + else + // Oops! There are multiple renders with different parents.... + Debug.LogWarning("Unclear model root"); + // We still return a model root, but this may not be the correct one... + } + return this.model != null; } #endregion Init #region Start + /// + /// Start the creature + /// protected virtual void Start() { this.CheckTargetRig(); this.CheckModel(); @@ -79,28 +98,38 @@ namespace Passer.CreatureControl { #region Update + /// + /// Update the creature + /// public virtual void Update() { if (this.targetRig == null) + // Without a target rig, the creature cannot move return; UpdatePose(); - // copy animator root motion to the humanoid + // copy animator root motion to the creature this.transform.SetPositionAndRotation(targetRig.transform.position, targetRig.transform.rotation); // As target rig is probably a child of this.transform, // We need to restore the position/rotation of the targetsRig. - targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation); + targetRig.transform.SetPositionAndRotation(this.transform.position, this.transform.rotation); } + /// + /// Update the pose of the creature using the target rig + /// public void UpdatePose() { if (this.targetRig == null) return; - this.targetRig.PoseLimbs(); - UpdateBones(); + this.targetRig.Pose(); + UpdateModel(); } - public virtual void UpdateBones() { + /// + /// Update the bones of the creature's rig from the target rig pose + /// + public virtual void UpdateModel() { Vector3 newPosition = this.targetRig.transform.position + this.targetToModelTranslation; Quaternion newOrientation = this.targetRig.transform.rotation * this.targetToModelRotation; this.model.SetPositionAndRotation(newPosition, newOrientation); @@ -108,12 +137,19 @@ namespace Passer.CreatureControl { #endregion Update + #region Scene view + + /// + /// Update the pose of the creature when the application is not running + /// void OnDrawGizmos() { // This ensures that the model is always following the target rig if (Application.isPlaying == false) { this.UpdatePose(); } } + + #endregion Scene view } } \ No newline at end of file diff --git a/CreatureControl/Runtime/Scripts/Insect/Insect.cs b/CreatureControl/Runtime/Scripts/Insect/Insect.cs index 4d0a88a..c9e7fca 100644 --- a/CreatureControl/Runtime/Scripts/Insect/Insect.cs +++ b/CreatureControl/Runtime/Scripts/Insect/Insect.cs @@ -31,8 +31,8 @@ namespace Passer.CreatureControl { #region Update - public override void UpdateBones() { - base.UpdateBones(); + public override void UpdateModel() { + base.UpdateModel(); if (this.insectRig.leftFrontLeg != null) this.insectRig.leftFrontLeg.UpdateBones(this.leftFrontLeg); diff --git a/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs b/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs index 68f4e57..0e51dae 100644 --- a/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs +++ b/CreatureControl/Runtime/Scripts/Insect/InsectRig.cs @@ -12,7 +12,7 @@ namespace Passer.CreatureControl { public TargetLeg rightMiddleLeg; public TargetLeg rightBackLeg; - public override void PoseLimbs() { + public override void Pose() { this.leftBackLeg.PoseLimb(); this.leftMiddleLeg.PoseLimb(); this.leftFrontLeg.PoseLimb(); diff --git a/CreatureControl/Runtime/Scripts/TargetLeg.cs b/CreatureControl/Runtime/Scripts/TargetLeg.cs index 9c8febb..5a7f9b4 100644 --- a/CreatureControl/Runtime/Scripts/TargetLeg.cs +++ b/CreatureControl/Runtime/Scripts/TargetLeg.cs @@ -14,6 +14,19 @@ namespace Passer.CreatureControl { public Quaternion targetToBoneFemur; public Quaternion targetToBoneTibia; + public float femurLength; + public float tibiaLength; + public float length; + + /// + /// Update the lenghts of the leg bones + /// + private void CalculateLengths() { + this.femurLength = Vector3.Distance(this.femurTarget.position, this.tibiaTarget.position); + this.tibiaLength = Vector3.Distance(this.tibiaTarget.position, this.tarsusTarget.position); + this.length = femurLength + tibiaLength; + } + public void MatchTo(Leg leg) { this.femurTarget.position = leg.femur.position; this.tibiaTarget.position = leg.tibia.position; @@ -21,15 +34,13 @@ namespace Passer.CreatureControl { targetToBoneFemur = TargetRig.TargetToBoneRotation(leg.femur, leg.tibia); targetToBoneTibia = TargetRig.TargetToBoneRotation(leg.tibia, leg.tarsus); - float femurLength = Vector3.Distance(this.femurTarget.position, this.tibiaTarget.position); - float tibiaLength = Vector3.Distance(this.tibiaTarget.position, this.tarsusTarget.position); - float leglength = femurLength + tibiaLength; + CalculateLengths(); + // Put the end-effector target for IK in a sensible place Vector3 legDirection = (this.tarsusTarget.position - this.femurTarget.position).normalized; - this.target.position = this.femurTarget.position + 0.7f * leglength * legDirection.normalized; - this.target.rotation = Quaternion.LookRotation(legDirection); - - //this.target.SetPositionAndRotation(this.tarsusTarget.position, this.tarsusTarget.rotation); + Vector3 targetPosition = this.femurTarget.position + 0.7f * this.length * legDirection.normalized; + Quaternion targetRotation = Quaternion.LookRotation(legDirection); + this.target.SetPositionAndRotation(targetPosition, targetRotation); this.target.localPosition = new(this.target.localPosition.x, 0, this.target.localPosition.z); } diff --git a/CreatureControl/Runtime/Scripts/TargetRig.cs b/CreatureControl/Runtime/Scripts/TargetRig.cs index 9b5956d..0232505 100644 --- a/CreatureControl/Runtime/Scripts/TargetRig.cs +++ b/CreatureControl/Runtime/Scripts/TargetRig.cs @@ -9,14 +9,26 @@ namespace Passer.CreatureControl { public Animator animator; - public virtual void PoseLimbs() { + /// + /// Pose the target rig using the IK targets + /// + public virtual void Pose() { } - + + /// + /// Align the target rig with a creature + /// + /// The creature to align to public void MatchTo(Creature creature) { bool anythingChangedDummy = false; MatchTo(creature, ref anythingChangedDummy); } + /// + /// Align the target rig with a creature + /// + /// The creature to align to + /// True when any property of the creature has changed public virtual void MatchTo(Creature creature, ref bool anythingChanged) { Vector3 targetToModelTranslation = creature.model.position - this.transform.position; bool changed = targetToModelTranslation != creature.targetToModelTranslation; @@ -33,6 +45,14 @@ namespace Passer.CreatureControl { } } + /// + /// Compute the rotation from the target bone to a creature's bone + /// + /// The creature's bone for this target bone + /// The next bone in the creature's hierarchy + /// The rotation from the target bone rotation to the creature bone rotation + /// The next bone is used to compute the direction of the bone. + /// The 'up'-direction of the bone is currently fixed to (world) up. public static Quaternion TargetToBoneRotation(Transform bone, Transform nextBone) { if (bone == null || nextBone == null) return Quaternion.identity; @@ -42,24 +62,6 @@ namespace Passer.CreatureControl { Quaternion toBoneRotation = Quaternion.Inverse(targetRotation) * bone.rotation; return toBoneRotation; } - - public static Quaternion TargetToBoneRotation(Transform target, Transform nextTarget, Transform bone) { - Vector3 direction = nextTarget.position - bone.position; - Quaternion targetRotation = Quaternion.LookRotation(direction, Vector3.up); - Quaternion toBoneRotation = Quaternion.Inverse(targetRotation) * bone.rotation; - return toBoneRotation; - } - - public static Quaternion TargetToBoneRotation_Debug(Transform target, Transform nextTarget, Transform bone) { - Vector3 direction = nextTarget.position - target.position; - Quaternion targetRotation = Quaternion.LookRotation(direction, Vector3.up); - Debug.DrawRay(bone.position, targetRotation * Vector3.forward, Color.blue); - Debug.DrawRay(bone.position, targetRotation * Vector3.up, Color.green); - - Quaternion toBoneRotation = Quaternion.Inverse(targetRotation) * bone.rotation; - Debug.Log($"{targetRotation.eulerAngles} {bone.rotation.eulerAngles} -> {toBoneRotation.eulerAngles}"); - return toBoneRotation; - } } } \ No newline at end of file