cleanup & documentation
This commit is contained in:
parent
837c5ce807
commit
fc581a0dd8
@ -1,51 +1,54 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Passer.CreatureControl {
|
||||
|
||||
[CustomEditor(typeof(Creature), true)]
|
||||
public class Creature_Editor : Editor {
|
||||
|
||||
/// <summary>
|
||||
/// The creature managed by this editor
|
||||
/// </summary>
|
||||
protected Creature creature;
|
||||
|
||||
#region Init
|
||||
#region Start
|
||||
|
||||
/// <summary>
|
||||
/// Enable the creature editor
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given creature is a prefab
|
||||
/// </summary>
|
||||
/// <param name="creature">The creature to check</param>
|
||||
/// <returns>True when it is a prefab</returns>
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -3,15 +3,24 @@ using UnityEngine;
|
||||
namespace Passer.CreatureControl {
|
||||
|
||||
public class Creature : MonoBehaviour {
|
||||
/// <summary>
|
||||
/// The (hopefully rigged) 3D model of the creature
|
||||
/// </summary>
|
||||
public Transform model;
|
||||
|
||||
/// <summary>The target bones rig</summary>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// The positional different between the target rig and model root
|
||||
/// </summary>
|
||||
public Vector3 targetToModelTranslation;
|
||||
/// <summary>
|
||||
/// The rotational difference between the target rig and the model root
|
||||
/// </summary>
|
||||
public Quaternion targetToModelRotation;
|
||||
|
||||
#region Init
|
||||
@ -20,27 +29,32 @@ namespace Passer.CreatureControl {
|
||||
/// Ensure a target rig is available
|
||||
/// </summary>
|
||||
/// <param name="targetRigResourceName">The name of the target rig resource</param>
|
||||
/// <returns>True when the creature rig has been updated</returns>
|
||||
/// <returns>True when the target rig has been updated</returns>
|
||||
/// 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<InsectRig>();
|
||||
if (this.targetRig == null) {
|
||||
GameObject targetsRigPrefab = Resources.Load<GameObject>(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<InsectRig>();
|
||||
}
|
||||
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<TargetRig>();
|
||||
if (this.targetRig == null) {
|
||||
// Nope, there is no target rig, so instantiate it using the given resource name
|
||||
GameObject targetsRigPrefab = Resources.Load<GameObject>(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<TargetRig>();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure a target rig is available
|
||||
/// </summary>
|
||||
/// <returns>True when the target rig has been updated</returns>
|
||||
/// This tries to instantiate the default target rig resource
|
||||
public virtual bool CheckTargetRig() {
|
||||
return CheckTargetRig("TargetRig");
|
||||
}
|
||||
@ -50,25 +64,30 @@ namespace Passer.CreatureControl {
|
||||
/// </summary>
|
||||
/// <returns>True when the creature rig has been updated</returns>
|
||||
public bool CheckModel() {
|
||||
if (this.model == null) {
|
||||
SkinnedMeshRenderer[] skinnedMeshRenderers = this.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
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<SkinnedMeshRenderer>();
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Start the creature
|
||||
/// </summary>
|
||||
protected virtual void Start() {
|
||||
this.CheckTargetRig();
|
||||
this.CheckModel();
|
||||
@ -79,28 +98,38 @@ namespace Passer.CreatureControl {
|
||||
|
||||
#region Update
|
||||
|
||||
/// <summary>
|
||||
/// Update the creature
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the pose of the creature using the target rig
|
||||
/// </summary>
|
||||
public void UpdatePose() {
|
||||
if (this.targetRig == null)
|
||||
return;
|
||||
|
||||
this.targetRig.PoseLimbs();
|
||||
UpdateBones();
|
||||
this.targetRig.Pose();
|
||||
UpdateModel();
|
||||
}
|
||||
|
||||
public virtual void UpdateBones() {
|
||||
/// <summary>
|
||||
/// Update the bones of the creature's rig from the target rig pose
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Update the pose of the creature when the application is not running
|
||||
/// </summary>
|
||||
void OnDrawGizmos() {
|
||||
// This ensures that the model is always following the target rig
|
||||
if (Application.isPlaying == false) {
|
||||
this.UpdatePose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Scene view
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -14,6 +14,19 @@ namespace Passer.CreatureControl {
|
||||
public Quaternion targetToBoneFemur;
|
||||
public Quaternion targetToBoneTibia;
|
||||
|
||||
public float femurLength;
|
||||
public float tibiaLength;
|
||||
public float length;
|
||||
|
||||
/// <summary>
|
||||
/// Update the lenghts of the leg bones
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -9,14 +9,26 @@ namespace Passer.CreatureControl {
|
||||
|
||||
public Animator animator;
|
||||
|
||||
public virtual void PoseLimbs() {
|
||||
/// <summary>
|
||||
/// Pose the target rig using the IK targets
|
||||
/// </summary>
|
||||
public virtual void Pose() {
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Align the target rig with a creature
|
||||
/// </summary>
|
||||
/// <param name="creature">The creature to align to</param>
|
||||
public void MatchTo(Creature creature) {
|
||||
bool anythingChangedDummy = false;
|
||||
MatchTo(creature, ref anythingChangedDummy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Align the target rig with a creature
|
||||
/// </summary>
|
||||
/// <param name="creature">The creature to align to</param>
|
||||
/// <param name="anythingChanged">True when any property of the creature has changed</param>
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the rotation from the target bone to a creature's bone
|
||||
/// </summary>
|
||||
/// <param name="bone">The creature's bone for this target bone</param>
|
||||
/// <param name="nextBone">The next bone in the creature's hierarchy</param>
|
||||
/// <returns>The rotation from the target bone rotation to the creature bone rotation</returns>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user