Skip to content

Commit

Permalink
Dealing with parent constraints setup on the object
Browse files Browse the repository at this point in the history
Some people want to setup objects that are Constraines to
the avatar being equipped.
This lead to the item following the avatar, even when enabling
"World Lock".

So the tool now automatically disables constraints components setup
on the objects during "World Lock".

Disabling constraints is still a bit rough, though, since it
disable all constraints, even those that have zero link to the
avatar.

I'll add more fine handling later.

Also, if the constraints sources of the object to equip link to
a Transform of the main avatar, these links are fixed in order to
point to the same Transform on the avatar copy.

Meaning that if you link your sword to the main avatar hands, they
will be linked to the duplicated avatar hands once equipped using
the Constraint tools.

Signed-off-by: Voyage <voyage@miouyouyou.fr>
  • Loading branch information
vr-voyage committed Nov 18, 2022
1 parent 426681d commit 35b5504
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 32 deletions.
10 changes: 4 additions & 6 deletions Constraints/SetupAvatarConstraints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,14 @@ public bool PrepareObjects(MyyAssetsManager runAssets)
return atLeastOnePrepared;
}

private void GenerateSetup(GameObject[] objectsToFix, bool lockAtWorldCenter)
private void GenerateSetup(GameObject[] objectsToFix, ConstraintsGlobalOptions options)
{
int nObjects = objectsToFix.Length;
objects = new SetupObjectConstraints[nObjects];

for (int i = 0; i < nObjects; i++)
{
objects[i] = new SetupObjectConstraints(objectsToFix[i], variableNamePrefix + i)
{
lockAtWorldCenter = lockAtWorldCenter
};
objects[i] = new SetupObjectConstraints(objectsToFix[i], variableNamePrefix + i, options);
}

}
Expand Down Expand Up @@ -167,6 +164,7 @@ private void AttachToAvatar(VRCAvatarDescriptor avatar, MyyAssetsManager runAsse
if (!toAttach.IsPrepared()) continue;

toAttach.AttachHierarchy(avatarCopy.gameObject);
toAttach.FixConstraintSources(avatar.gameObject, avatarCopy.gameObject);
toAttach.SetupStations(stationsProxies);
string variableName = toAttach.animVariableName;

Expand Down Expand Up @@ -232,7 +230,7 @@ public void Setup(VRCAvatarDescriptor avatar, ConstraintsGlobalOptions options,

/* FIXME Manage the global options correctly
*/
GenerateSetup(objectsToFix, options.lockAtWorldOrigin);
GenerateSetup(objectsToFix, options);

MyyAssetsManager runAssets = PrepareRun(avatar);
if (runAssets == null)
Expand Down
132 changes: 112 additions & 20 deletions Constraints/SetupObjectConstraints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class SetupObjectConstraints : SetupObject
const string containerName = "Container";

public GameObject copy;
public bool lockAtWorldCenter = false;
public ConstraintsGlobalOptions options = ConstraintsGlobalOptions.Default();

public enum ClipIndex
{
Expand Down Expand Up @@ -171,7 +171,7 @@ public void AddCurves(AnimationClip off, AnimationClip on)
* offCurves are set to index 0
* onCurves are set to index 1
* curve[0] will have Component.m_Enabled set to 0
* curve[1] will have COmponent.m_Enabled set to 1
* curve[1] will have Component.m_Enabled set to 1
*/

for (int i = 0; i < clips.Length; i++)
Expand All @@ -195,9 +195,10 @@ public static int VRCParametersCost()



public SetupObjectConstraints(GameObject go, string variableName)
public SetupObjectConstraints(GameObject go, string variableName, ConstraintsGlobalOptions options)
: base(go, variableName)
{
this.options = options;
additionalHierarchy.name = worldLockSuffix + "-" + animVariableName;
clips = new AnimationClip[(int)ClipIndex.COUNT];
machines = new AnimatorStateMachine[]
Expand Down Expand Up @@ -232,7 +233,7 @@ private string PathToParentConstraint()

private string PathToContainer()
{
if (!lockAtWorldCenter)
if (!options.lockAtWorldOrigin)
{
return $"{PathToParentConstraint()}/{containerName}";
}
Expand All @@ -243,6 +244,51 @@ private string PathToContainer()

}

public void FixConstraintSources(GameObject mainAvatar, GameObject avatarCopy)
{
List<ConstraintSource> constraintSources = new List<ConstraintSource>();
foreach (var constraint in copy.GetComponentsInChildren<IConstraint>())
{
constraintSources.Clear();

/* Get the sources
* Check if they refer to a member of the mainAvatar
* If that's the case, find the same member in the copy
* and set it as the new source.
*/
constraint.GetSources(constraintSources);

int nSources = constraintSources.Count;
for (int i = 0; i < nSources; i++)
{
/* For each source transform set on the object,
* Get the related GameObject (if any). */
var source = constraintSources[i];
GameObject sourceObject = source.sourceTransform.gameObject;
if (sourceObject == null) continue;

/* Check if there's a direct path between the source GameObject and
* the maih Avatar */
string relativePath = sourceObject.PathFrom(mainAvatar);
if (relativePath == null) continue;

/* Try to use the same relative path from the avatar copy, to
* find a similar GameObject Transform. */
Transform copyTransform = avatarCopy.transform.Find(relativePath);

if (copyTransform == null) continue;

/* Use the found GameObject Transform copy as the new source */
source.sourceTransform = copyTransform;
constraintSources[i] = source; // Structure needs to be copied back
}

/* Set the potentially modified sources back */
constraint.SetSources(constraintSources);

}
}

public void AttachHierarchy(GameObject avatar)
{
GameObject worldLock = additionalHierarchy;
Expand All @@ -264,18 +310,22 @@ public void AttachHierarchy(GameObject avatar)
name = containerName
};
/* Default state to non-active (OFF), so that
* people disabling custom animations don't see
* the objects
*/
lockedContainer.SetActive(false);
* people disabling custom animations don't see
* the objects
*/
if (options.hideWhenOff)
{
lockedContainer.SetActive(false);
}


avatar.transform.position = new Vector3(0, 0, 0);
lockedContainer.transform.position = new Vector3(0, 0, 0);

Transform rootTransform = avatar.transform;

/* So the logic is : Lerp(AvatarPosition, -AvatarPosition, 0.5)
* which provide the middlepoint between your avatar position and its opposite. */
* which provide the middlepoint between your avatar position and its opposite. */
posConstraint.AddSource(rootTransform, -1);

/* Here, it's black magic : Any negative value will freeze the rotation */
Expand All @@ -284,7 +334,7 @@ public void AttachHierarchy(GameObject avatar)
worldLock.transform.parent = avatar.transform;

GameObject containerParent = worldLock;
if (!lockAtWorldCenter)
if (!options.lockAtWorldOrigin)
{
/* Ye standard setup */
GameObject parentLock = new GameObject
Expand All @@ -308,16 +358,38 @@ public void AttachHierarchy(GameObject avatar)
fixedCopy.SetActive(true);

string objectPath = $"{PathToContainer()}/{fixedCopy.name}";
Vector3 actualPosition = fixedCopy.transform.localPosition;
Vector3 actualScale = fixedCopy.transform.localScale;
fixedCopy.transform.localScale = Vector3.zero;
fixedCopy.transform.localPosition = Vector3.zero;
if (options.hideWhenOff)
{
Vector3 actualPosition = fixedCopy.transform.localPosition;
Vector3 actualScale = fixedCopy.transform.localScale;
fixedCopy.transform.localScale = Vector3.zero;
fixedCopy.transform.localPosition = Vector3.zero;
clips[(int)ClipIndex.OFF].SetCurve(objectPath, typeof(Transform), "m_LocalPosition", Vector3.zero);
clips[(int)ClipIndex.OFF].SetCurve(objectPath, typeof(Transform), "m_LocalScale", Vector3.zero);
clips[(int)ClipIndex.ON].SetCurve(objectPath, typeof(Transform), "m_LocalPosition", actualPosition);
clips[(int)ClipIndex.ON].SetCurve(objectPath, typeof(Transform), "m_LocalScale", actualScale);
}

clips[(int)ClipIndex.OFF].SetCurve(objectPath, typeof(Transform), "m_LocalPosition", Vector3.zero);
clips[(int)ClipIndex.OFF].SetCurve(objectPath, typeof(Transform), "m_LocalScale", Vector3.zero);
clips[(int)ClipIndex.ON].SetCurve(objectPath, typeof(Transform), "m_LocalPosition", actualPosition);
clips[(int)ClipIndex.ON].SetCurve(objectPath, typeof(Transform), "m_LocalScale", actualScale);

foreach (Transform t in fixedCopy.GetComponentsInChildren<Transform>())
{
GameObject go = t.gameObject;
string relativePathFromContainer = go.PathFrom(lockedContainer);
if (relativePathFromContainer == null)
{
Debug.LogWarning($"Cannot retrieve the relative path between {lockedContainer.name} and {go.name}");
continue;
}
string lockedObjectPath = $"{PathToContainer()}/{go.PathFrom(lockedContainer)}";

foreach (var constraint in go.GetComponentsInChildren<IConstraint>())
{
clips[(int)ClipIndex.ON].SetCurve(
lockedObjectPath, constraint.GetType(), "m_Enabled", ConstantCurve(false));
clips[(int)ClipIndex.OFF].SetCurve(
lockedObjectPath, constraint.GetType(), "m_Enabled", ConstantCurve(true));
}
}
AssetDatabase.SaveAssets();

this.copy = fixedCopy;
Expand All @@ -326,9 +398,29 @@ public void AttachHierarchy(GameObject avatar)
public bool GenerateAnims()
{
string containerPath = PathToContainer();
GenerateAnimations(assetManager, clips,
((int)ClipIndex.OFF, "OFF", new AnimProperties()),
((int)ClipIndex.ON, "ON", new AnimProperties())
);

if (options.hideWhenOff)
{
clips[(int)ClipIndex.ON].SetCurve(
containerPath, typeof(GameObject), "m_IsActive", ConstantCurve(true));
clips[(int)ClipIndex.OFF].SetCurve(
containerPath, typeof(GameObject), "m_IsActive", ConstantCurve(false));
}

if (!lockAtWorldCenter)
if (!options.lockAtWorldOrigin)
{
string constraintPath = PathToParentConstraint();
clips[(int)ClipIndex.ON].SetCurve(
constraintPath, typeof(ParentConstraint), "m_Active", ConstantCurve(false));
clips[(int)ClipIndex.OFF].SetCurve(
constraintPath, typeof(ParentConstraint), "m_Active", ConstantCurve(true));
}
/* !!! options.hideWhenOff */
/*if (!options.lockAtWorldOrigin)
{
string constraintPath = PathToParentConstraint();
GenerateAnimations(assetManager, clips,
Expand All @@ -350,7 +442,7 @@ public bool GenerateAnims()
((int)ClipIndex.ON, "ON", new AnimProperties(
(containerPath, typeof(GameObject), "m_IsActive", ConstantCurve(true))
)));
}
}*/


/* TODO Find a better way to factorize this */
Expand Down
3 changes: 2 additions & 1 deletion MyyHelpers/MyyAnimHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Myy
*/
public struct AnimProperties
{
(string path, System.Type type, string fieldName, AnimationCurve curve)[] curves;
readonly (string path, System.Type type, string fieldName, AnimationCurve curve)[] curves;

/**
* <summary>Define animation properties, using LISP like syntax.
Expand Down Expand Up @@ -47,6 +47,7 @@ public AnimProperties(params (string path, System.Type type, string fieldName, A
*/
public void AddTo(AnimationClip clip)
{
if (curves == null) return;
foreach (var curve in curves)
{
clip.SetCurve(curve.path, curve.type, curve.fieldName, curve.curve);
Expand Down
80 changes: 80 additions & 0 deletions MyyHelpers/MyyObjectHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,86 @@ public static void KeepOnlyComponents(this GameObject gameObject, params System.
}
}
}

/**
* <summary>Returns the parent of this GameObject, if any</summary>
*
* <returns>
* <para>
* The parent of this GameObject,
* or null if no transform parent were found.
* </para>
* </returns>
*/
public static GameObject GetParent(this GameObject gameObject)
{
Transform parentTransform = gameObject.transform.parent;

return (parentTransform != null ? parentTransform.gameObject : null);
}

/**
* <summary>
* Get the Animation (or transform.Find) relative path from the provided object,
* to this object
* </summary>
*
* <param name="from">The object to compute the relative path from</param>
*
* <returns>
* The relative path from the provided object, to this one, or an empty string
* if <paramref name="from"/> is not a parent of this GameObject.
* </returns>
*/
public static string PathFrom(this GameObject gameObject, GameObject from)
{
if (from == gameObject)
{
return "";
}

List<string> path = new List<string>(8);
GameObject currentObject = gameObject;

do
{
path.Add(currentObject.name);
currentObject = currentObject.GetParent();
} while ((currentObject != null) & (currentObject != from));

/* No direct path were found */
if (currentObject == null)
{
return "";
}

path.Reverse();
return string.Join("/", path);
}

/**
* <summary>Checks if this object is a child of the provided object.</summary>
*
* <param name="potentialParent">The GameObject to check relationship to</param>
*
* <returns>
* <para>Returns true if this GameObject is a child of <paramref name="potentialParent"/></para>
* <para>Returns false otherwise.</para>
* </returns>
*/
public static bool IsChildOf(this GameObject gameObject, GameObject potentialParent)
{
GameObject currentParent = gameObject.GetParent();
while (currentParent != null)
{
if (currentParent == potentialParent)
{
return true;
}
currentParent = currentParent.GetParent();
}
return false;
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion SetupConstraintsWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ namespace Myy
public struct ConstraintsGlobalOptions
{
public bool lockAtWorldOrigin;
public bool hideWhenOff;

public static ConstraintsGlobalOptions Default()
{
return new ConstraintsGlobalOptions() { lockAtWorldOrigin = true, hideWhenOff = false };
}
}
public class SetupConstraintsWindow : EditorWindow
{
Expand All @@ -26,6 +32,7 @@ public class SetupConstraintsWindow : EditorWindow
*/
//public ConstraintsGlobalOptions options;
public bool lockAtWorldOrigin = false;
public bool hiddenWhenOff = true;
public GameObject[] worldLockedObjects = new GameObject[1];
public UnityEngine.Object saveDir;

Expand Down Expand Up @@ -193,6 +200,7 @@ private void OnEnable()
(Translate(StringID.Label_AvatarToConfigure), "avatar", AvatarUseable),
(Translate(StringID.Label_ObjectToLock), "worldLockedObjects", WorldLockedObjectsUseable),
(Translate(StringID.Label_LockAtWorldOrigin), "lockAtWorldOrigin", null),
(Translate(StringID.Label_HiddenWhenOff), "hiddenWhenOff", null),
(Translate(StringID.Label_SaveDirectory), "saveDir", SaveDirectoryValid));
}

Expand All @@ -216,7 +224,8 @@ private void OnGUI()
{
ConstraintsGlobalOptions options = new ConstraintsGlobalOptions()
{
lockAtWorldOrigin = lockAtWorldOrigin
lockAtWorldOrigin = lockAtWorldOrigin,
hideWhenOff = hiddenWhenOff
};
string saveDirPath = AssetDatabase.GetAssetPath(saveDir);
SetupAvatarConstraints setupTool = new SetupAvatarConstraints();
Expand Down
Loading

0 comments on commit 35b5504

Please sign in to comment.