/* Copyright (c) 2020-2022 Lyuma Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; using VRC.SDK3.Avatars.Components; using VRC.SDK3.Avatars.ScriptableObjects; using VRC.SDK3.Dynamics.Contact.Components; using VRC.SDK3.Dynamics.PhysBone.Components; // [RequireComponent(typeof(Animator))] public class LyumaAv3Runtime : MonoBehaviour { static public Dictionary animLayerToDefaultController = new Dictionary(); static public Dictionary animLayerToDefaultAvaMask = new Dictionary(); public delegate void UpdateSelectionFunc(UnityEngine.Object obj); public static UpdateSelectionFunc updateSelectionDelegate; public delegate void AddRuntime(Component runtime); public static AddRuntime addRuntimeDelegate; public delegate void UpdateSceneLayersFunc(int layers); public static UpdateSceneLayersFunc updateSceneLayersDelegate; public delegate void ApplyOnEnableWorkaroundDelegateType(); public static ApplyOnEnableWorkaroundDelegateType ApplyOnEnableWorkaroundDelegate; public LyumaAv3Runtime OriginalSourceClone = null; [Tooltip("Resets avatar state machine instantly")] public bool ResetAvatar; [Tooltip("Resets avatar state machine and waits until you uncheck this to start")] public bool ResetAndHold; [Tooltip("Click if you modified your menu or parameter list")] public bool RefreshExpressionParams; [Tooltip("Simulates saving and reloading the avatar")] public bool KeepSavedParametersOnReset = true; [HideInInspector] public bool legacyMenuGUI = true; private bool lastLegacyMenuGUI = true; [Header("Animator to Debug. Unity is glitchy when not 'Base'.")] [Tooltip("Selects the playable layer to be visible with parameters in the Animator. If you view any other playable in the Animator window, parameters will say 0 and will not update.")] public VRCAvatarDescriptor.AnimLayerType DebugDuplicateAnimator; private char PrevAnimatorToDebug; [Tooltip("Selects the playable layer to be visible in Unity's Animator window. Does not reset avatar. Unless this is set to Base, will cause 'Invalid Layer Index' logspam; layers will show wrong weight and parameters will all be 0.")] public VRCAvatarDescriptor.AnimLayerType ViewAnimatorOnlyNoParams; private char PrevAnimatorToViewLiteParamsShow0; [HideInInspector] public string SourceObjectPath; [HideInInspector] public LyumaAv3Runtime AvatarSyncSource; private float nextUpdateTime = 0.0f; [Header("OSC (double click OSC Controller for debug and port settings)")] public bool EnableAvatarOSC = false; public LyumaAv3Osc OSCController = null; public A3EOSCConfiguration OSCConfigurationFile = new A3EOSCConfiguration(); [Header("Network Clones and Sync")] public bool CreateNonLocalClone; [Tooltip("In VRChat, 8-bit float quantization only happens remotely. Check this to test your robustness to quantization locally, too. (example: 0.5 -> 0.503")] public bool locally8bitQuantizedFloats = false; private int CloneCount; [Range(0.0f, 2.0f)] public float NonLocalSyncInterval = 0.2f; [Tooltip("Parameters visible in the radial menu will IK sync")] public bool IKSyncRadialMenu = true; [Header("PlayerLocal and MirrorReflection")] public bool EnableHeadScaling; public bool DisableMirrorAndShadowClones; [HideInInspector] public LyumaAv3Runtime MirrorClone; [HideInInspector] public LyumaAv3Runtime ShadowClone; [Tooltip("To view both copies at once")] public bool DebugOffsetMirrorClone = false; public bool ViewMirrorReflection; private bool LastViewMirrorReflection; public bool ViewBothRealAndMirror; private bool LastViewBothRealAndMirror; [HideInInspector] public VRCAvatarDescriptor avadesc; Avatar animatorAvatar; Animator animator; private RuntimeAnimatorController origAnimatorController; public Dictionary allControllers = new Dictionary(); private Transform[] allTransforms; private Transform[] allMirrorTransforms; private Transform[] allShadowTransforms; private List playables = new List(); private List> playableParamterIds = new List>(); private List> playableParamterFloats = new List>(); private List> playableParamterInts = new List>(); private List> playableParamterBools = new List>(); AnimationLayerMixerPlayable playableMixer; PlayableGraph playableGraph; VRCExpressionsMenu expressionsMenu; VRCExpressionParameters stageParameters; int sittingIndex, tposeIndex, ikposeIndex; int fxIndex, altFXIndex; int actionIndex, altActionIndex; int additiveIndex, altAdditiveIndex; int gestureIndex, altGestureIndex; private int mouthOpenBlendShapeIdx; private int[] visemeBlendShapeIdxs; [NonSerialized] public VRCPhysBone[] AvDynamicsPhysBones = new VRCPhysBone[]{}; [NonSerialized] public VRCContactReceiver[] AvDynamicsContactReceivers = new VRCContactReceiver[]{}; public class Av3EmuParameterAccess : VRC.SDKBase.IAnimParameterAccess { public LyumaAv3Runtime runtime; public string paramName; public bool boolVal { get { // Debug.Log(paramName + " GETb"); int idx; if (runtime.IntToIndex.TryGetValue(paramName, out idx)) return runtime.Ints[idx].value != 0; if (runtime.FloatToIndex.TryGetValue(paramName, out idx))return runtime.Floats[idx].exportedValue != 0.0f; if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) return runtime.Bools[idx].value; return false; } set { // Debug.Log(paramName + " SETb " + value); int idx; if (runtime.IntToIndex.TryGetValue(paramName, out idx)) runtime.Ints[idx].value = value ? 1 : 0; if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) { runtime.Floats[idx].value = value ? 1.0f : 0.0f; runtime.Floats[idx].exportedValue = runtime.Floats[idx].value; } if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) runtime.Bools[idx].value = value; } } public int intVal { get { int idx; // Debug.Log(paramName + " GETi"); if (runtime.IntToIndex.TryGetValue(paramName, out idx)) return runtime.Ints[idx].value; if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) return (int)runtime.Floats[idx].exportedValue; if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) return runtime.Bools[idx].value ? 1 : 0; return 0; } set { // Debug.Log(paramName + " SETi " + value); int idx; if (runtime.IntToIndex.TryGetValue(paramName, out idx)) runtime.Ints[idx].value = value; if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) { runtime.Floats[idx].value = (float)value; runtime.Floats[idx].exportedValue = runtime.Floats[idx].value; } if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) runtime.Bools[idx].value = value != 0; } } public float floatVal { get { // Debug.Log(paramName + " GETf"); int idx; if (runtime.IntToIndex.TryGetValue(paramName, out idx)) return (float)runtime.Ints[idx].value; if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) return runtime.Floats[idx].exportedValue; if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) return runtime.Bools[idx].value ? 1.0f : 0.0f; return 0.0f; } set { // Debug.Log(paramName + " SETf " + value); int idx; if (runtime.IntToIndex.TryGetValue(paramName, out idx)) runtime.Ints[idx].value = (int)value; if (runtime.FloatToIndex.TryGetValue(paramName, out idx)) { runtime.Floats[idx].value = value; runtime.Floats[idx].exportedValue = value; } if (runtime.BoolToIndex.TryGetValue(paramName, out idx)) runtime.Bools[idx].value = value != 0.0f; } } } public void assignContactParameters(VRCContactReceiver[] behaviours) { AvDynamicsContactReceivers = behaviours; foreach (var mb in AvDynamicsContactReceivers) { var old_value = mb.paramAccess; if (old_value == null || old_value.GetType() != typeof(Av3EmuParameterAccess)) { string parameter = mb.parameter; Av3EmuParameterAccess accessInst = new Av3EmuParameterAccess(); accessInst.runtime = this; accessInst.paramName = parameter; mb.paramAccess = accessInst; accessInst.floatVal = mb.paramValue; // Debug.Log("Assigned access " + contactReceiverState.paramAccess.GetValue(mb) + " to param " + parameter + ": was " + old_value); } } } public void assignPhysBoneParameters(VRCPhysBone[] behaviours) { AvDynamicsPhysBones = behaviours; foreach (var mb in AvDynamicsPhysBones) { var old_value = mb.param_Stretch; if (old_value == null || old_value.GetType() != typeof(Av3EmuParameterAccess)) { string parameter = mb.parameter; Av3EmuParameterAccess accessInst = new Av3EmuParameterAccess(); accessInst.runtime = this; accessInst.paramName = parameter + VRCPhysBone.PARAM_ANGLE; mb.param_Angle = accessInst; accessInst.floatVal = mb.param_AngleValue; accessInst = new Av3EmuParameterAccess(); accessInst.runtime = this; accessInst.paramName = parameter + VRCPhysBone.PARAM_ISGRABBED; mb.param_IsGrabbed = accessInst; accessInst.boolVal = mb.param_IsGrabbedValue; accessInst = new Av3EmuParameterAccess(); accessInst.runtime = this; accessInst.paramName = parameter + VRCPhysBone.PARAM_STRETCH; mb.param_Stretch = accessInst; accessInst.floatVal = mb.param_StretchValue; // Debug.Log("Assigned strech access " + physBoneState.param_Stretch.GetValue(mb) + " to param " + parameter + ": was " + old_value); } } } public static float ClampFloatOnly(float val) { if (val < -1.0f) { val = -1.0f; } if (val > 1.0f) { val = 1.0f; } return val; } public static float ClampAndQuantizeFloat(float val) { val = ClampFloatOnly(val); val *= 127.00f; // if (val > 127.0f) { // val = 127.0f; // } val = Mathf.Round(val); val = (((sbyte)val) / 127.0f); val = ClampFloatOnly(val); return val; } public static int ClampByte(int val) { if (val < 0) { val = 0; } if (val > 255) { val = 255; } return val; } public enum VisemeIndex { sil, PP, FF, TH, DD, kk, CH, SS, nn, RR, aa, E, I, O, U } public enum GestureIndex { Neutral, Fist, HandOpen, Fingerpoint, Victory, RockNRoll, HandGun, ThumbsUp } public enum TrackingTypeIndex { Uninitialized, GenericRig, NoFingers, HeadHands, HeadHandsHip, HeadHandsHipFeet = 6 } public static HashSet BUILTIN_PARAMETERS = new HashSet { "Viseme", "GestureLeft", "GestureLeftWeight", "GestureRight", "GestureRightWeight", "VelocityX", "VelocityY", "VelocityZ", "Upright", "AngularY", "Grounded", "Seated", "AFK", "TrackingType", "VRMode", "MuteSelf", "InStation" }; public static readonly HashSet MirrorCloneComponentBlacklist = new HashSet { typeof(Camera), typeof(FlareLayer), typeof(AudioSource), typeof(Rigidbody), typeof(Joint) }; public static readonly HashSet ShadowCloneComponentBlacklist = new HashSet { typeof(Camera), typeof(FlareLayer), typeof(AudioSource), typeof(Light), typeof(ParticleSystemRenderer), typeof(Rigidbody), typeof(Joint) }; [Header("Built-in inputs / Viseme")] public VisemeIndex Viseme; [Range(0, 15)] public int VisemeIdx; private int VisemeInt; [Tooltip("Voice amount from 0.0f to 1.0f for the current viseme")] [Range(0,1)] public float Voice; [Header("Built-in inputs / Hand Gestures")] public GestureIndex GestureLeft; [Range(0, 9)] public int GestureLeftIdx; private char GestureLeftIdxInt; [Range(0, 1)] public float GestureLeftWeight; private float OldGestureLeftWeight; public GestureIndex GestureRight; [Range(0, 9)] public int GestureRightIdx; private char GestureRightIdxInt; [Range(0, 1)] public float GestureRightWeight; private float OldGestureRightWeight; [Header("Built-in inputs / Locomotion")] public Vector3 Velocity; [Range(-400, 400)] public float AngularY; [Range(0, 1)] public float Upright; public bool Grounded; public bool Jump; public float JumpPower = 5; public float RunSpeed = 0.0f; private bool WasJump; private Vector3 JumpingHeight; private Vector3 JumpingVelocity; private bool PrevSeated, PrevTPoseCalibration, PrevIKPoseCalibration; public bool Seated; public bool AFK; public bool TPoseCalibration; public bool IKPoseCalibration; [Header("Built-in inputs / Tracking Setup and Other")] public TrackingTypeIndex TrackingType; [Range(0, 6)] public int TrackingTypeIdx; private char TrackingTypeIdxInt; public bool VRMode; public bool MuteSelf; private bool MuteTogglerOn; public bool InStation; [HideInInspector] public int AvatarVersion = 3; [Header("Output State (Read-only)")] public bool IsLocal; [HideInInspector] public bool IsMirrorClone; [HideInInspector] public bool IsShadowClone; public bool LocomotionIsDisabled; [Serializable] public struct IKTrackingOutput { public Vector3 HeadRelativeViewPosition; public Vector3 ViewPosition; public float AvatarScaleFactorGuess; public VRCAnimatorTrackingControl.TrackingType trackingHead; public VRCAnimatorTrackingControl.TrackingType trackingLeftHand; public VRCAnimatorTrackingControl.TrackingType trackingRightHand; public VRCAnimatorTrackingControl.TrackingType trackingHip; public VRCAnimatorTrackingControl.TrackingType trackingLeftFoot; public VRCAnimatorTrackingControl.TrackingType trackingRightFoot; public VRCAnimatorTrackingControl.TrackingType trackingLeftFingers; public VRCAnimatorTrackingControl.TrackingType trackingRightFingers; public VRCAnimatorTrackingControl.TrackingType trackingEyesAndEyelids; public VRCAnimatorTrackingControl.TrackingType trackingMouthAndJaw; } public IKTrackingOutput IKTrackingOutputData; [Serializable] public class FloatParam { [HideInInspector] public string stageName; public string name; [HideInInspector] public bool synced; [Range(-1, 1)] public float expressionValue; [HideInInspector] public float lastExpressionValue_; [Range(-1, 1)] public float value; [HideInInspector] private float exportedValue_; public float exportedValue { get { return exportedValue_; } set { this.exportedValue_ = value; this.value = value; this.lastExpressionValue_ = value; this.expressionValue = value; } } [HideInInspector] public float lastValue; } [Header("User-generated inputs")] public List Floats = new List(); public Dictionary FloatToIndex = new Dictionary(); [Serializable] public class IntParam { [HideInInspector] public string stageName; public string name; [HideInInspector] public bool synced; public int value; [HideInInspector] public int lastValue; } public List Ints = new List(); public Dictionary IntToIndex = new Dictionary(); [Serializable] public class BoolParam { [HideInInspector] public string stageName; public string name; [HideInInspector] public bool synced; public bool value; [HideInInspector] public bool lastValue; [HideInInspector] public bool[] hasTrigger; [HideInInspector] public bool[] hasBool; } public List Bools = new List(); public Dictionary BoolToIndex = new Dictionary(); public Dictionary StageParamterToBuiltin = new Dictionary(); [HideInInspector] public LyumaAv3Emulator emulator; static public Dictionary animatorToTopLevelRuntime = new Dictionary(); private HashSet attachedAnimators; private HashSet duplicateParameterAdds = new HashSet(); const float BASE_HEIGHT = 1.4f; public IEnumerator DelayedEnterPoseSpace(bool setView, float time) { yield return new WaitForSeconds(time); if (setView) { Transform head = animator.GetBoneTransform(HumanBodyBones.Head); if (head != null) { IKTrackingOutputData.ViewPosition = animator.transform.InverseTransformPoint(head.TransformPoint(IKTrackingOutputData.HeadRelativeViewPosition)); } } else { IKTrackingOutputData.ViewPosition = avadesc.ViewPosition; } } class BlendingState { float startWeight; float goalWeight; float blendStartTime; float blendDuration; public bool blending; public float UpdateBlending() { if (blendDuration <= 0) { blending = false; return goalWeight; } float amt = (Time.time - blendStartTime) / blendDuration; if (amt >= 1) { blending = false; return goalWeight; } return Mathf.Lerp(startWeight, goalWeight, amt); } public void StartBlend(float startWeight, float goalWeight, float duration) { this.startWeight = startWeight; this.blendDuration = duration; this.blendStartTime = Time.time; this.goalWeight = goalWeight; this.blending = true; } } class PlayableBlendingState : BlendingState { public List layerBlends = new List(); } List playableBlendingStates = new List(); static HashSet issuedWarningAnimators = new HashSet(); static bool getTopLevelRuntime(string component, Animator innerAnimator, out LyumaAv3Runtime runtime) { if (animatorToTopLevelRuntime.TryGetValue(innerAnimator, out runtime)) { return true; } Transform transform = innerAnimator.transform; while (transform != null && runtime == null) { runtime = transform.GetComponent(); transform = transform.parent; } if (runtime != null) { if (runtime.attachedAnimators != null) { Debug.Log("[" + component + "]: " + innerAnimator + " found parent runtime after it was Awoken! Adding to cache. Did you move me?"); animatorToTopLevelRuntime.Add(innerAnimator, runtime); runtime.attachedAnimators.Add(innerAnimator); } else { Debug.Log("[" + component + "]: " + innerAnimator + " found parent runtime without being Awoken! Wakey Wakey...", runtime); runtime.Awake(); } return true; } if (!issuedWarningAnimators.Contains(innerAnimator)) { issuedWarningAnimators.Add(innerAnimator); Debug.LogWarning("[" + component + "]: outermost Animator is not known: " + innerAnimator + ". If you changed something, consider resetting avatar", innerAnimator); } return false; } float getAdjustedParameterAsFloat(string paramName, bool convertRange=false, float srcMin=0.0f, float srcMax=0.0f, float dstMin=0.0f, float dstMax=0.0f) { float newValue = 0; int idx; if (FloatToIndex.TryGetValue(paramName, out idx)) { newValue = Floats[idx].exportedValue; } else if (IntToIndex.TryGetValue(paramName, out idx)) { newValue = (float)Ints[idx].value; } else if (BoolToIndex.TryGetValue(paramName, out idx)) { newValue = Bools[idx].value ? 1.0f : 0.0f; } if (convertRange) { if (dstMax != dstMin) { newValue = Mathf.Lerp(dstMin, dstMax, Mathf.Clamp01(Mathf.InverseLerp(srcMin, srcMax, newValue))); } else { newValue = dstMin; } } return newValue; } static LyumaAv3Runtime() { VRCAvatarParameterDriver.OnApplySettings += (behaviour, animator) => { LyumaAv3Runtime runtime; if (!getTopLevelRuntime("VRCAvatarParameterDriver", animator, out runtime)) { return; } if (runtime.IsMirrorClone || runtime.IsShadowClone) { return; } if (behaviour.debugString != null && behaviour.debugString.Length > 0) { Debug.Log("[VRCAvatarParameterDriver:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); } if (!runtime) { return; } if (animator != runtime.animator && (!runtime.emulator || !runtime.emulator.legacySubAnimatorParameterDriverMode)) { return; } if (!runtime.IsLocal && behaviour.localOnly) { return; } HashSet newParameterAdds = new HashSet(); HashSet deleteParameterAdds = new HashSet(); foreach (var parameter in behaviour.parameters) { if (runtime.DebugDuplicateAnimator != VRCAvatarDescriptor.AnimLayerType.Base && !runtime.IsMirrorClone && !runtime.IsShadowClone && (parameter.type == VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add || parameter.type == VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random)) { string dupeKey = parameter.value + ((parameter.type == VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add) ? "add " : "rand ") + parameter.name; if (!runtime.duplicateParameterAdds.Contains(dupeKey)) { newParameterAdds.Add(dupeKey); continue; } deleteParameterAdds.Add(dupeKey); } string actualName = parameter.name; int idx; if (runtime.IntToIndex.TryGetValue(actualName, out idx)) { switch (parameter.type) { case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set: runtime.Ints[idx].value = (int)parameter.value; break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add: runtime.Ints[idx].value += (int)parameter.value; break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: runtime.Ints[idx].value = UnityEngine.Random.Range((int)parameter.valueMin, (int)parameter.valueMax + 1); break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: runtime.Ints[idx].value = (int)runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax); break; } } if (runtime.FloatToIndex.TryGetValue(actualName, out idx)) { switch (parameter.type) { case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set: runtime.Floats[idx].exportedValue = parameter.value; break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add: runtime.Floats[idx].exportedValue += parameter.value; break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: runtime.Floats[idx].exportedValue = UnityEngine.Random.Range(parameter.valueMin, parameter.valueMax); break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: runtime.Floats[idx].exportedValue = runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax); break; } runtime.Floats[idx].value = runtime.Floats[idx].exportedValue; } if (runtime.BoolToIndex.TryGetValue(actualName, out idx)) { bool newValue; BoolParam bp = runtime.Bools[idx]; int whichController; // bp.value = parameter.value != 0; switch (parameter.type) { case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Set: newValue = parameter.value != 0.0f; break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Add: /* editor script treats it as random, but it is its own operation */ newValue = ((bp.value ? 1.0 : 0.0) + parameter.value) != 0.0f; // weird but ok... // Debug.Log("Add bool " + bp.name + " to " + newValue + ", " + (bp.value ? 1.0 : 0.0) + ", " + parameter.value); break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Random: // random is *not* idempotent. newValue = UnityEngine.Random.Range(0.0f, 1.0f) < parameter.chance; break; case VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType.Copy: newValue = runtime.getAdjustedParameterAsFloat(parameter.source, parameter.convertRange, parameter.sourceMin, parameter.sourceMax, parameter.destMin, parameter.destMax) != 0.0f; break; default: continue; } if (!bp.synced) { // Triggers ignore alue and Set unconditionally. whichController = 0; foreach (var p in runtime.playables) { if (bp.hasBool[whichController]) { p.SetBool(actualName, newValue); } whichController++; } whichController = 0; foreach (var p in runtime.playables) { if (bp.hasTrigger[whichController]) { p.SetTrigger(actualName); } whichController++; } bp.lastValue = newValue; } bp.value = newValue; } } foreach (var key in deleteParameterAdds) { runtime.duplicateParameterAdds.Remove(key); } foreach (var key in newParameterAdds) { runtime.duplicateParameterAdds.Add(key); } }; VRCPlayableLayerControl.Initialize += (x) => { x.ApplySettings += (behaviour, animator) => { LyumaAv3Runtime runtime; if (!getTopLevelRuntime("VRCPlayableLayerControl", animator, out runtime)) { return; } if (runtime.IsMirrorClone && runtime.IsShadowClone) { return; } if (behaviour.debugString != null && behaviour.debugString.Length > 0) { Debug.Log("[VRCPlayableLayerControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); } if (!runtime) { return; } int idx = -1; switch (behaviour.layer) { case VRCPlayableLayerControl.BlendableLayer.Action: idx = runtime.actionIndex; break; case VRCPlayableLayerControl.BlendableLayer.Additive: idx = runtime.additiveIndex; break; case VRCPlayableLayerControl.BlendableLayer.FX: idx = runtime.fxIndex; break; case VRCPlayableLayerControl.BlendableLayer.Gesture: idx = runtime.gestureIndex; break; } if (idx >= 0 && idx < runtime.playableBlendingStates.Count) { runtime.playableBlendingStates[idx].StartBlend(runtime.playableMixer.GetInputWeight(idx + 1), behaviour.goalWeight, behaviour.blendDuration); // Debug.Log("Start blend of whole playable " + idx + " from " + runtime.playableMixer.GetInputWeight(idx + 1) + " to " + behaviour.goalWeight); } }; }; VRCAnimatorLayerControl.Initialize += (x) => { x.ApplySettings += (behaviour, animator) => { LyumaAv3Runtime runtime; if (!getTopLevelRuntime("VRCAnimatorLayerControl", animator, out runtime)) { return; } if (runtime.IsMirrorClone) { return; } if (behaviour.debugString != null && behaviour.debugString.Length > 0) { Debug.Log("[VRCAnimatorLayerControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); } if (!runtime) { return; } int idx = -1, altidx = -1; switch (behaviour.playable) { case VRCAnimatorLayerControl.BlendableLayer.Action: idx = runtime.actionIndex; altidx = runtime.altActionIndex; break; case VRCAnimatorLayerControl.BlendableLayer.Additive: idx = runtime.additiveIndex; altidx = runtime.altAdditiveIndex; break; case VRCAnimatorLayerControl.BlendableLayer.FX: idx = runtime.fxIndex; altidx = runtime.altFXIndex; break; case VRCAnimatorLayerControl.BlendableLayer.Gesture: idx = runtime.gestureIndex; altidx = runtime.altGestureIndex; break; } if (idx >= 0 && idx < runtime.playableBlendingStates.Count) { if (behaviour.layer >= 0 && behaviour.layer < runtime.playableBlendingStates[idx].layerBlends.Count) { runtime.playableBlendingStates[idx].layerBlends[behaviour.layer].StartBlend(runtime.playables[idx].GetLayerWeight(behaviour.layer), behaviour.goalWeight, behaviour.blendDuration); // Debug.Log("Start blend of playable " + idx + " layer " + behaviour.layer + " from " + runtime.playables[idx].GetLayerWeight(behaviour.layer) + " to " + behaviour.goalWeight); if (altidx >= 0) { runtime.playableBlendingStates[altidx].layerBlends[behaviour.layer].StartBlend(runtime.playables[altidx].GetLayerWeight(behaviour.layer), behaviour.goalWeight, behaviour.blendDuration); // Debug.Log("Start blend of alt playable " + altidx + " layer " + behaviour.layer + " from " + runtime.playables[altidx].GetLayerWeight(behaviour.layer) + " to " + behaviour.goalWeight); } } } }; }; VRCAnimatorLocomotionControl.Initialize += (x) => { x.ApplySettings += (behaviour, animator) => { LyumaAv3Runtime runtime; if (!getTopLevelRuntime("VRCAnimatorLocomotionControl", animator, out runtime)) { return; } if (runtime.IsMirrorClone && runtime.IsShadowClone) { return; } if (behaviour.debugString != null && behaviour.debugString.Length > 0) { Debug.Log("[VRCAnimatorLocomotionControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); } if (!runtime) { return; } // I legit don't know runtime.LocomotionIsDisabled = behaviour.disableLocomotion; }; }; VRCAnimatorTemporaryPoseSpace.Initialize += (x) => { x.ApplySettings += (behaviour, animator) => { LyumaAv3Runtime runtime; if (!getTopLevelRuntime("VRCAnimatorSetView", animator, out runtime)) { return; } if (runtime.IsMirrorClone && runtime.IsShadowClone) { return; } if (behaviour.debugString != null && behaviour.debugString.Length > 0) { Debug.Log("[VRCAnimatorSetView:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); } if (!runtime) { return; } // fixedDelay: Is the delay fixed or normalized... // The layerIndex is not passed into the delegate, so we cannot reimplement fixedDelay. runtime.StartCoroutine(runtime.DelayedEnterPoseSpace(behaviour.enterPoseSpace, behaviour.delayTime)); }; }; VRCAnimatorTrackingControl.Initialize += (x) => { x.ApplySettings += (behaviour, animator) => { LyumaAv3Runtime runtime; if (!getTopLevelRuntime("VRCAnimatorTrackingControl", animator, out runtime)) { return; } if (runtime.IsMirrorClone && runtime.IsShadowClone) { return; } if (behaviour.debugString != null && behaviour.debugString.Length > 0) { Debug.Log("[VRCAnimatorTrackingControl:" + (runtime == null ? "null" : runtime.name) + "]" + behaviour.name + ": " + behaviour.debugString, behaviour); } if (!runtime) { return; } if (behaviour.trackingMouth != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingMouthAndJaw = behaviour.trackingMouth; } if (behaviour.trackingHead != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingHead = behaviour.trackingHead; } if (behaviour.trackingRightFingers != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingRightFingers = behaviour.trackingRightFingers; } if (behaviour.trackingEyes != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingEyesAndEyelids = behaviour.trackingEyes; } if (behaviour.trackingLeftFingers != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingLeftFingers = behaviour.trackingLeftFingers; } if (behaviour.trackingLeftFoot != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingLeftFoot = behaviour.trackingLeftFoot; } if (behaviour.trackingHip != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingHip = behaviour.trackingHip; } if (behaviour.trackingRightHand != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingRightHand = behaviour.trackingRightHand; } if (behaviour.trackingLeftHand != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingLeftHand = behaviour.trackingLeftHand; } if (behaviour.trackingRightFoot != VRCAnimatorTrackingControl.TrackingType.NoChange) { runtime.IKTrackingOutputData.trackingRightFoot = behaviour.trackingRightFoot; } }; }; } void OnDestroy () { if (this.playableGraph.IsValid()) { this.playableGraph.Destroy(); } if (attachedAnimators != null) { foreach (var anim in attachedAnimators) { LyumaAv3Runtime runtime; if (animatorToTopLevelRuntime.TryGetValue(anim, out runtime) && runtime == this) { animatorToTopLevelRuntime.Remove(anim); } } } if (animator != null) { if (animator.playableGraph.IsValid()) { animator.playableGraph.Destroy(); } animator.runtimeAnimatorController = origAnimatorController; } } void Awake() { if (AvatarSyncSource != null && OriginalSourceClone == null) { Debug.Log("Awake returning early for " + gameObject.name, this); return; } if (attachedAnimators != null) { Debug.Log("Deduplicating Awake() call if we already got awoken by our children.", this); return; } // Debug.Log("AWOKEN " + gameObject.name, this); attachedAnimators = new HashSet(); if (AvatarSyncSource == null) { var oml = GetComponent(); if (oml != null && oml.startTransform != null) { this.emulator = oml.startTransform.GetComponent(); GameObject.DestroyImmediate(oml); } Transform transform = this.transform; SourceObjectPath = ""; while (transform != null) { SourceObjectPath = "/" + transform.name + SourceObjectPath; transform = transform.parent; } AvatarSyncSource = this; } else { AvatarSyncSource = GameObject.Find(SourceObjectPath).GetComponent(); } if (this.emulator != null) { DebugDuplicateAnimator = this.emulator.DefaultAnimatorToDebug; ViewAnimatorOnlyNoParams = this.emulator.DefaultAnimatorToDebug; } animator = this.gameObject.GetOrAddComponent(); if (animatorAvatar != null && animator.avatar == null) { animator.avatar = animatorAvatar; } else { animatorAvatar = animator.avatar; } // Default values. Grounded = true; Upright = 1.0f; if (!animator.isHuman) { TrackingType = TrackingTypeIndex.GenericRig; } else if (!VRMode) { TrackingType = TrackingTypeIndex.HeadHands; } avadesc = this.gameObject.GetComponent(); if (avadesc.VisemeSkinnedMesh == null) { mouthOpenBlendShapeIdx = -1; visemeBlendShapeIdxs = new int[0]; } else { mouthOpenBlendShapeIdx = avadesc.VisemeSkinnedMesh.sharedMesh.GetBlendShapeIndex(avadesc.MouthOpenBlendShapeName); visemeBlendShapeIdxs = new int[avadesc.VisemeBlendShapes == null ? 0 : avadesc.VisemeBlendShapes.Length]; if (avadesc.VisemeBlendShapes != null) { for (int i = 0; i < avadesc.VisemeBlendShapes.Length; i++) { visemeBlendShapeIdxs[i] = avadesc.VisemeSkinnedMesh.sharedMesh.GetBlendShapeIndex(avadesc.VisemeBlendShapes[i]); } } } bool shouldClone = false; if (OriginalSourceClone == null) { OriginalSourceClone = this; shouldClone = true; } if (shouldClone && GetComponent() == null) { GameObject cloned = GameObject.Instantiate(gameObject); cloned.hideFlags = HideFlags.HideAndDontSave; cloned.SetActive(false); OriginalSourceClone = cloned.GetComponent(); Debug.Log("Spawned a hidden source clone " + OriginalSourceClone, OriginalSourceClone); OriginalSourceClone.OriginalSourceClone = OriginalSourceClone; } foreach (var smr in gameObject.GetComponentsInChildren(true)) { smr.updateWhenOffscreen = (AvatarSyncSource == this || IsMirrorClone || IsShadowClone); } int desiredLayer = 9; if (AvatarSyncSource == this) { desiredLayer = 10; } if (IsMirrorClone) { desiredLayer = 18; } if (IsShadowClone) { desiredLayer = 9; // the Shadowclone is always on playerLocal and never on UI Menu } if (gameObject.layer != 12 || desiredLayer == 18) { gameObject.layer = desiredLayer; } allTransforms = gameObject.GetComponentsInChildren(true); foreach (Transform t in allTransforms) { if (t.gameObject.layer != 12 || desiredLayer == 18) { t.gameObject.layer = desiredLayer; } } InitializeAnimator(); if (addRuntimeDelegate != null) { addRuntimeDelegate(this); } if (AvatarSyncSource == this) { CreateAv3MenuComponent(); } if (this.AvatarSyncSource != this || IsMirrorClone || IsShadowClone) { PrevAnimatorToViewLiteParamsShow0 = (char)(int)ViewAnimatorOnlyNoParams; } if (!IsMirrorClone && !IsShadowClone && AvatarSyncSource == this) { var pipelineManager = avadesc.GetComponent(); string avatarid = pipelineManager != null ? pipelineManager.blueprintId : null; OSCConfigurationFile.EnsureOSCJSONConfig(avadesc.expressionParameters, avatarid, this.gameObject.name); } } public void CreateMirrorClone() { if (AvatarSyncSource == this && GetComponent() == null) { OriginalSourceClone.IsMirrorClone = true; MirrorClone = GameObject.Instantiate(OriginalSourceClone.gameObject).GetComponent(); MirrorClone.GetComponent().avatar = null; OriginalSourceClone.IsMirrorClone = false; GameObject o = MirrorClone.gameObject; o.name = gameObject.name + " (MirrorReflection)"; o.SetActive(true); allMirrorTransforms = MirrorClone.gameObject.GetComponentsInChildren(true); foreach (Component component in MirrorClone.gameObject.GetComponentsInChildren(true)) { if (MirrorCloneComponentBlacklist.Contains(component.GetType()) || component.GetType().ToString().Contains("DynamicBone") || component.GetType().ToString().Contains("VRCContact") || component.GetType().ToString().Contains("VRCPhysBone")) { UnityEngine.Object.Destroy(component); } } } } public void CreateShadowClone() { if (AvatarSyncSource == this && GetComponent() == null) { OriginalSourceClone.IsShadowClone = true; ShadowClone = GameObject.Instantiate(OriginalSourceClone.gameObject).GetComponent(); ShadowClone.GetComponent().avatar = null; OriginalSourceClone.IsShadowClone = false; GameObject o = ShadowClone.gameObject; o.name = gameObject.name + " (ShadowClone)"; o.SetActive(true); allShadowTransforms = ShadowClone.gameObject.GetComponentsInChildren(true); foreach (Component component in ShadowClone.gameObject.GetComponentsInChildren(true)) { if (ShadowCloneComponentBlacklist.Contains(component.GetType()) || component.GetType().ToString().Contains("DynamicBone") || component.GetType().ToString().Contains("VRCContact") || component.GetType().ToString().Contains("VRCPhysBone")) { UnityEngine.Object.Destroy(component); continue; } if (component.GetType() == typeof(SkinnedMeshRenderer) || component.GetType() == typeof(MeshRenderer)) { Renderer renderer = component as Renderer; renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly; // ShadowCastingMode.TwoSided isn't accounted for and does not work locally } } foreach (Renderer renderer in gameObject.GetComponentsInChildren(true)) { renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; // ShadowCastingMode.TwoSided isn't accounted for and does not work locally } } } private void InitializeAnimator() { ResetAvatar = false; PrevAnimatorToDebug = (char)(int)DebugDuplicateAnimator; ViewAnimatorOnlyNoParams = DebugDuplicateAnimator; animator = this.gameObject.GetOrAddComponent(); animator.avatar = animatorAvatar; animator.applyRootMotion = false; animator.updateMode = AnimatorUpdateMode.Normal; animator.cullingMode = (this == AvatarSyncSource || IsMirrorClone || IsShadowClone) ? AnimatorCullingMode.AlwaysAnimate : AnimatorCullingMode.CullCompletely; animator.runtimeAnimatorController = null; avadesc = this.gameObject.GetComponent(); IKTrackingOutputData.ViewPosition = avadesc.ViewPosition; IKTrackingOutputData.AvatarScaleFactorGuess = IKTrackingOutputData.ViewPosition.magnitude / BASE_HEIGHT; // mostly guessing... IKTrackingOutputData.HeadRelativeViewPosition = IKTrackingOutputData.ViewPosition; if (animator.avatar != null) { Transform head = animator.GetBoneTransform(HumanBodyBones.Head); if (head != null) { IKTrackingOutputData.HeadRelativeViewPosition = head.InverseTransformPoint(animator.transform.TransformPoint(IKTrackingOutputData.ViewPosition)); } } expressionsMenu = avadesc.expressionsMenu; stageParameters = avadesc.expressionParameters; if (origAnimatorController != null) { origAnimatorController = animator.runtimeAnimatorController; } VRCAvatarDescriptor.CustomAnimLayer[] baselayers = avadesc.baseAnimationLayers; VRCAvatarDescriptor.CustomAnimLayer[] speciallayers = avadesc.specialAnimationLayers; List allLayers = new List(); // foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { // if (AnimatorToDebug == cal.type) { // allLayers.Add(cal); // } // } // foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { // if (AnimatorToDebug == cal.type) { // allLayers.Add(cal); // } // } int i = 0; if (DebugDuplicateAnimator != VRCAvatarDescriptor.AnimLayerType.Base && !IsMirrorClone && !IsShadowClone) { foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { if (DebugDuplicateAnimator == cal.type) { i++; allLayers.Add(cal); break; } } foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { if (DebugDuplicateAnimator == cal.type) { i++; allLayers.Add(cal); break; } } // WE ADD ALL THE LAYERS A SECOND TIME BECAUSE! // Add and Random Parameter drivers are not idepotent. // To solve this, we ignore every other invocation. // Therefore, we must add all layers twice, not just the one we are debugging...??? foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { if (DebugDuplicateAnimator != cal.type) { i++; allLayers.Add(cal); } } foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { if (DebugDuplicateAnimator != cal.type) { i++; allLayers.Add(cal); } } } int dupeOffset = i; if (!IsMirrorClone && !IsShadowClone) { foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { if (cal.type == VRCAvatarDescriptor.AnimLayerType.Base || cal.type == VRCAvatarDescriptor.AnimLayerType.Additive) { i++; allLayers.Add(cal); } } foreach (VRCAvatarDescriptor.CustomAnimLayer cal in speciallayers) { i++; allLayers.Add(cal); } } foreach (VRCAvatarDescriptor.CustomAnimLayer cal in baselayers) { if (IsMirrorClone || IsShadowClone) { if (cal.type == VRCAvatarDescriptor.AnimLayerType.FX) { i++; allLayers.Add(cal); } } else if (!(cal.type == VRCAvatarDescriptor.AnimLayerType.Base || cal.type == VRCAvatarDescriptor.AnimLayerType.Additive)) { i++; allLayers.Add(cal); } } if (playableGraph.IsValid()) { playableGraph.Destroy(); } playables.Clear(); playableBlendingStates.Clear(); for (i = 0; i < allLayers.Count; i++) { playables.Add(new AnimatorControllerPlayable()); playableBlendingStates.Add(null); } actionIndex = fxIndex = gestureIndex = additiveIndex = sittingIndex = ikposeIndex = tposeIndex = -1; altActionIndex = altFXIndex = altGestureIndex = altAdditiveIndex = -1; foreach (var anim in attachedAnimators) { LyumaAv3Runtime runtime; if (animatorToTopLevelRuntime.TryGetValue(anim, out runtime) && runtime == this) { animatorToTopLevelRuntime.Remove(anim); } } attachedAnimators.Clear(); Animator[] animators = this.gameObject.GetComponentsInChildren(true); foreach (Animator anim in animators) { attachedAnimators.Add(anim); animatorToTopLevelRuntime.Add(anim, this); } Dictionary stageNameToValue = EarlyRefreshExpressionParameters(); if (animator.playableGraph.IsValid()) { animator.playableGraph.Destroy(); } // var director = avadesc.gameObject.GetComponent(); playableGraph = PlayableGraph.Create("LyumaAvatarRuntime - " + this.gameObject.name); var externalOutput = AnimationPlayableOutput.Create(playableGraph, "ExternalAnimator", animator); playableMixer = AnimationLayerMixerPlayable.Create(playableGraph, allLayers.Count + 1); externalOutput.SetSourcePlayable(playableMixer); animator.applyRootMotion = false; i = 0; // playableMixer.ConnectInput(0, AnimatorControllerPlayable.Create(playableGraph, allLayers[layerToDebug - 1].animatorController), 0, 0); foreach (VRCAvatarDescriptor.CustomAnimLayer vrcAnimLayer in allLayers) { i++; // Ignore zeroth layer. bool additive = (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Additive); RuntimeAnimatorController ac = null; AvatarMask mask; if (vrcAnimLayer.isDefault) { ac = animLayerToDefaultController[vrcAnimLayer.type]; mask = animLayerToDefaultAvaMask[vrcAnimLayer.type]; } else { ac = vrcAnimLayer.animatorController; mask = vrcAnimLayer.mask; if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.FX) { mask = animLayerToDefaultAvaMask[vrcAnimLayer.type]; // Force mask to prevent muscle overrides. } } if (ac == null) { Debug.Log(vrcAnimLayer.type + " controller is null: continue."); // i was incremented, but one of the playableMixer inputs is left unconnected. continue; } allControllers[vrcAnimLayer.type] = ac; AnimatorControllerPlayable humanAnimatorPlayable = AnimatorControllerPlayable.Create(playableGraph, ac); PlayableBlendingState pbs = new PlayableBlendingState(); for (int j = 0; j < humanAnimatorPlayable.GetLayerCount(); j++) { pbs.layerBlends.Add(new BlendingState()); } // If we are debugging a particular layer, we must put that first. // The Animator Controller window only shows the first layer. int effectiveIdx = i; playableMixer.ConnectInput((int)effectiveIdx, humanAnimatorPlayable, 0, 1); playables[effectiveIdx - 1] = humanAnimatorPlayable; playableBlendingStates[effectiveIdx - 1] = pbs; if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Sitting) { if (i >= dupeOffset) { sittingIndex = effectiveIdx - 1; } playableMixer.SetInputWeight(effectiveIdx, 0f); } if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.IKPose) { if (i >= dupeOffset) { ikposeIndex = effectiveIdx - 1; } playableMixer.SetInputWeight(effectiveIdx, 0f); } if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.TPose) { if (i >= dupeOffset) { tposeIndex = effectiveIdx - 1; } playableMixer.SetInputWeight(effectiveIdx, 0f); } if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Action) { playableMixer.SetInputWeight(i, 0f); if (i < dupeOffset) { altActionIndex = effectiveIdx - 1; } else { actionIndex = effectiveIdx - 1; } } if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Gesture) { if (i < dupeOffset) { altGestureIndex = effectiveIdx - 1; } else { gestureIndex = effectiveIdx - 1; } } if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.Additive) { if (i < dupeOffset) { altAdditiveIndex = effectiveIdx - 1; } else { additiveIndex = effectiveIdx - 1; } } if (vrcAnimLayer.type == VRCAvatarDescriptor.AnimLayerType.FX) { if (i < dupeOffset) { altFXIndex = effectiveIdx - 1; } else { fxIndex = effectiveIdx - 1; } } // AnimationControllerLayer acLayer = new AnimationControllerLayer() if (mask != null) { playableMixer.SetLayerMaskFromAvatarMask((uint)effectiveIdx, mask); } if (additive) { playableMixer.SetLayerAdditive((uint)effectiveIdx, true); } // Keep weight 1.0 if (i < dupeOffset). // Layers have incorrect AAP values if playable weight is 0.0... // and the duplicate layers will be overridden later anyway by Base. } LateRefreshExpressionParameters(stageNameToValue); // Plays the Graph. playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime); Debug.Log(this.name + " : " + GetType() + " awoken and ready to Play.", this); playableGraph.Play(); } Dictionary EarlyRefreshExpressionParameters() { Dictionary stageNameToValue = new Dictionary(); if (IsLocal) { foreach (var val in Ints) { stageNameToValue[val.stageName] = val.value; } foreach (var val in Floats) { stageNameToValue[val.stageName] = val.exportedValue; } foreach (var val in Bools) { stageNameToValue[val.stageName] = val.value ? 1.0f : 0.0f; } } Ints.Clear(); Bools.Clear(); Floats.Clear(); StageParamterToBuiltin.Clear(); IntToIndex.Clear(); FloatToIndex.Clear(); BoolToIndex.Clear(); playableParamterFloats.Clear(); playableParamterIds.Clear(); playableParamterInts.Clear(); playableParamterBools.Clear(); return stageNameToValue; } void LateRefreshExpressionParameters(Dictionary stageNameToValue) { HashSet usedparams = new HashSet(BUILTIN_PARAMETERS); int i = 0; if (stageParameters != null) { int stageId = 0; foreach (var stageParam in stageParameters.parameters) { stageId++; // one-indexed if (stageParam.name == null || stageParam.name.Length == 0) { continue; } string stageName = stageParam.name + (stageParam.saved ? " (saved/SYNCED)" : " (SYNCED)"); //"Stage" + stageId; float lastDefault = 0.0f; if (AvatarSyncSource == this) { lastDefault = (stageParam.saved && KeepSavedParametersOnReset && stageNameToValue.ContainsKey(stageName) ? stageNameToValue[stageName] : stageParam.defaultValue); } StageParamterToBuiltin.Add(stageName, stageParam.name); if ((int)stageParam.valueType == 0) { IntParam param = new IntParam(); param.stageName = stageName; param.synced = true; param.name = stageParam.name; param.value = (int)lastDefault; param.lastValue = 0; IntToIndex[param.name] = Ints.Count; Ints.Add(param); } else if ((int)stageParam.valueType == 1) { FloatParam param = new FloatParam(); param.stageName = stageName; param.synced = true; param.name = stageParam.name; param.value = lastDefault; param.exportedValue = lastDefault; param.lastValue = 0; FloatToIndex[param.name] = Floats.Count; Floats.Add(param); } else if ((int)stageParam.valueType == 2) { BoolParam param = new BoolParam(); param.stageName = stageName; param.synced = true; param.name = stageParam.name; param.value = lastDefault != 0.0; param.lastValue = false; param.hasBool = new bool[playables.Count]; param.hasTrigger = new bool[playables.Count]; BoolToIndex[param.name] = Bools.Count; Bools.Add(param); } usedparams.Add(stageParam.name); i++; } } else { IntParam param = new IntParam(); param.stageName = "VRCEmote"; param.synced = true; param.name = "VRCEmote"; Ints.Add(param); usedparams.Add("VRCEmote"); FloatParam fparam = new FloatParam(); fparam.stageName = "VRCFaceBlendH"; fparam.synced = true; fparam.name = "VRCFaceBlendH"; Floats.Add(fparam); usedparams.Add("VRCFaceBlendH"); fparam = new FloatParam(); fparam.stageName = "VRCFaceBlendV"; fparam.synced = true; fparam.name = "VRCFaceBlendV"; Floats.Add(fparam); usedparams.Add("VRCFaceBlendV"); } //playableParamterIds int whichcontroller = 0; playableParamterIds.Clear(); foreach (AnimatorControllerPlayable playable in playables) { Dictionary parameterIndices = new Dictionary(); playableParamterInts.Add(new Dictionary()); playableParamterFloats.Add(new Dictionary()); playableParamterBools.Add(new Dictionary()); // Debug.Log("SETUP index " + whichcontroller + " len " + playables.Count); playableParamterIds.Add(parameterIndices); int pcnt = playable.IsValid() ? playable.GetParameterCount() : 0; for (i = 0; i < pcnt; i++) { AnimatorControllerParameter aparam = playable.GetParameter(i); string actualName; if (!StageParamterToBuiltin.TryGetValue(aparam.name, out actualName)) { actualName = aparam.name; } parameterIndices[actualName] = aparam.nameHash; if (usedparams.Contains(actualName)) { if (BoolToIndex.ContainsKey(aparam.name) && aparam.type == AnimatorControllerParameterType.Bool) { Bools[BoolToIndex[aparam.name]].hasBool[whichcontroller] = true; } if (BoolToIndex.ContainsKey(aparam.name) && aparam.type == AnimatorControllerParameterType.Trigger) { Bools[BoolToIndex[aparam.name]].hasTrigger[whichcontroller] = true; } continue; } if (aparam.type == AnimatorControllerParameterType.Int) { IntParam param = new IntParam(); param.stageName = aparam.name + " (local)"; param.synced = false; param.name = aparam.name; param.value = aparam.defaultInt; param.lastValue = param.value; IntToIndex[param.name] = Ints.Count; Ints.Add(param); usedparams.Add(aparam.name); } else if (aparam.type == AnimatorControllerParameterType.Float) { FloatParam param = new FloatParam(); param.stageName = aparam.name + " (local)"; param.synced = false; param.name = aparam.name; param.value = aparam.defaultFloat; param.exportedValue = aparam.defaultFloat; param.lastValue = param.value; FloatToIndex[param.name] = Floats.Count; Floats.Add(param); usedparams.Add(aparam.name); } else if (aparam.type == AnimatorControllerParameterType.Trigger || aparam.type == AnimatorControllerParameterType.Bool) { BoolParam param = new BoolParam(); param.stageName = aparam.name + " (local)"; param.synced = false; param.name = aparam.name; param.value = aparam.defaultBool; param.lastValue = param.value; param.hasBool = new bool[playables.Count]; param.hasTrigger = new bool[playables.Count]; param.hasBool[whichcontroller] = aparam.type == AnimatorControllerParameterType.Bool; param.hasTrigger[whichcontroller] = aparam.type == AnimatorControllerParameterType.Trigger; BoolToIndex[param.name] = Bools.Count; Bools.Add(param); usedparams.Add(aparam.name); } } whichcontroller++; } } void CreateAv3MenuComponent() { System.Type gestureManagerMenu = System.Type.GetType("GestureManagerAv3Menu"); if (gestureManagerMenu != null) { foreach (var comp in avadesc.gameObject.GetComponents(gestureManagerMenu)) { UnityEngine.Object.Destroy(comp); } } foreach (var comp in avadesc.gameObject.GetComponents()) { UnityEngine.Object.Destroy(comp); } LyumaAv3Menu mainMenu; if (gestureManagerMenu != null) { mainMenu = (LyumaAv3Menu)avadesc.gameObject.AddComponent(gestureManagerMenu); mainMenu.useLegacyMenu = legacyMenuGUI; } else { mainMenu = avadesc.gameObject.AddComponent(); } mainMenu.Runtime = this; mainMenu.RootMenu = avadesc.expressionsMenu; } private bool isResetting; private bool isResettingHold; private bool isResettingSel; void LateUpdate() { if (ResetAndHold || (emulator != null && (!emulator.enabled || !emulator.gameObject.activeInHierarchy))) { return; } if (IsMirrorClone || IsShadowClone) { // Experimental. Attempt to reproduce the 1-frame desync in some cases between normal and mirror copy. NormalUpdate(); } if(animator != null && this == AvatarSyncSource && !IsMirrorClone && !IsShadowClone) { if (MirrorClone != null) { MirrorClone.gameObject.SetActive(true); MirrorClone.transform.localRotation = transform.localRotation; MirrorClone.transform.localScale = transform.localScale; MirrorClone.transform.position = transform.position + (DebugOffsetMirrorClone ? new Vector3(0.0f, 1.3f * avadesc.ViewPosition.y, 0.0f) : Vector3.zero); } if (ShadowClone != null) { ShadowClone.gameObject.SetActive(true); ShadowClone.transform.localRotation = transform.localRotation; ShadowClone.transform.localScale = transform.localScale; ShadowClone.transform.position = transform.position; } foreach (Transform[] allXTransforms in new Transform[][]{allMirrorTransforms, allShadowTransforms}) { if (allXTransforms != null) { Transform head = animator.GetBoneTransform(HumanBodyBones.Head); for(int i = 0; i < allTransforms.Length && i < allXTransforms.Length; i++) { if (allXTransforms[i] == null || allTransforms[i] == this.transform) { continue; } MeshRenderer mr = allTransforms[i].GetComponent(); MeshRenderer xmr = allXTransforms[i].GetComponent(); if (mr != null && xmr != null) { for (int mri = 0; mri < mr.sharedMaterials.Length && mri < xmr.sharedMaterials.Length; mri++) { xmr.sharedMaterials[mri] = mr.sharedMaterials[mri]; } } allXTransforms[i].localPosition = allTransforms[i].localPosition; allXTransforms[i].localRotation = allTransforms[i].localRotation; if(allTransforms[i] == head && EnableHeadScaling) { allXTransforms[i].localScale = new Vector3(1.0f, 1.0f, 1.0f); } else { allXTransforms[i].localScale = allTransforms[i].localScale; } bool theirs = allTransforms[i].gameObject.activeSelf; if (allXTransforms[i].gameObject.activeSelf != theirs) { allXTransforms[i].gameObject.SetActive(theirs); } } } } } } void FixedUpdate() { if (Jump && !WasJump && Grounded) { JumpingVelocity = new Vector3(0.0f, JumpPower, 0.0f); JumpingHeight += JumpingVelocity; Grounded = false; } WasJump = Jump; if (JumpingHeight != Vector3.zero) { JumpingHeight += JumpingVelocity; JumpingVelocity += Physics.gravity * Time.fixedDeltaTime; if (JumpingHeight.y <= 0.0f) { JumpingHeight = Vector3.zero; JumpingVelocity = Vector3.zero; Grounded = true; Jump = false; WasJump = false; } Velocity.y = JumpingVelocity.y; } } private bool broadcastStartNextFrame; void OnEnable() { if (emulator != null && emulator.WorkaroundPlayModeScriptCompile) { ApplyOnEnableWorkaroundDelegate(); } if (attachedAnimators == null && AvatarSyncSource != null) { broadcastStartNextFrame = true; } } void Update() { if (broadcastStartNextFrame) { Debug.Log("BROADCASTING START!"); broadcastStartNextFrame = false; BroadcastMessage("Start"); } if (emulator != null && (!emulator.enabled || !emulator.gameObject.activeInHierarchy)) { return; } if (!IsMirrorClone && !IsShadowClone) { NormalUpdate(); } } // Update is called once per frame void NormalUpdate() { if (OSCConfigurationFile.OSCAvatarID == null) { OSCConfigurationFile.OSCAvatarID = A3EOSCConfiguration.AVTR_EMULATOR_PREFIX + "Default"; } if ((OSCConfigurationFile.UseRealPipelineIdJSONFile && OSCConfigurationFile.OSCAvatarID.StartsWith(A3EOSCConfiguration.AVTR_EMULATOR_PREFIX)) || (!OSCConfigurationFile.UseRealPipelineIdJSONFile && !OSCConfigurationFile.OSCAvatarID.StartsWith(A3EOSCConfiguration.AVTR_EMULATOR_PREFIX))) { var pipelineManager = avadesc.GetComponent(); string avatarid = pipelineManager != null ? pipelineManager.blueprintId : null; OSCConfigurationFile.EnsureOSCJSONConfig(avadesc.expressionParameters, avatarid, this.gameObject.name); } if (OSCConfigurationFile.SaveOSCConfig) { OSCConfigurationFile.SaveOSCConfig = false; A3EOSCConfiguration.WriteJSON(OSCConfigurationFile.OSCFilePath, OSCConfigurationFile.OSCJsonConfig); } if (OSCConfigurationFile.LoadOSCConfig) { OSCConfigurationFile.LoadOSCConfig = false; OSCConfigurationFile.OSCJsonConfig = A3EOSCConfiguration.ReadJSON(OSCConfigurationFile.OSCFilePath); } if (OSCConfigurationFile.GenerateOSCConfig) { OSCConfigurationFile.GenerateOSCConfig = false; OSCConfigurationFile.OSCJsonConfig = A3EOSCConfiguration.GenerateOuterJSON(avadesc.expressionParameters, OSCConfigurationFile.OSCAvatarID, this.gameObject.name); } if (lastLegacyMenuGUI != legacyMenuGUI && AvatarSyncSource == this) { lastLegacyMenuGUI = legacyMenuGUI; foreach (var av3MenuComponent in GetComponents()) { av3MenuComponent.useLegacyMenu = legacyMenuGUI; } } if (isResettingSel) { isResettingSel = false; if (updateSelectionDelegate != null && AvatarSyncSource == this) { updateSelectionDelegate(this.gameObject); PrevAnimatorToViewLiteParamsShow0 = (char)126; } } if (isResettingHold && (!ResetAvatar || !ResetAndHold)) { ResetAndHold = ResetAvatar = false; isResettingSel = true; if (updateSelectionDelegate != null && AvatarSyncSource == this) { updateSelectionDelegate(this.emulator != null ? this.emulator.gameObject : null); PrevAnimatorToViewLiteParamsShow0 = (char)126; } } if (ResetAvatar && ResetAndHold) { return; } if (ResetAndHold && !ResetAvatar && !isResetting) { ResetAvatar = true; isResettingHold = true; } if (isResetting && !ResetAndHold) { if (attachedAnimators == null) { if (AvatarSyncSource == this) { AvatarSyncSource = null; } Awake(); isResetting = false; isResettingHold = false; return; } else { InitializeAnimator(); } isResetting = false; isResettingHold = false; } if (PrevAnimatorToDebug != (char)(int)DebugDuplicateAnimator || ResetAvatar || attachedAnimators == null) { actionIndex = fxIndex = gestureIndex = additiveIndex = sittingIndex = ikposeIndex = tposeIndex = -1; altActionIndex = altFXIndex = altGestureIndex = altAdditiveIndex = -1; // animator.runtimeAnimatorController = null; if (playableGraph.IsValid()) { playableGraph.Destroy(); } if (animator.playableGraph.IsValid()) { animator.playableGraph.Destroy(); } animator.Update(0); animator.Rebind(); animator.Update(0); animator.StopPlayback(); GameObject.DestroyImmediate(animator); // animator.runtimeAnimatorController = EmptyController; if (updateSelectionDelegate != null && AvatarSyncSource == this) { updateSelectionDelegate(this.emulator != null ? this.emulator.gameObject : null); } isResetting = true; isResettingSel = true; return; } if (PrevAnimatorToViewLiteParamsShow0 == (char)127) { updateSelectionDelegate(this); ViewAnimatorOnlyNoParams = (VRCAvatarDescriptor.AnimLayerType)(int)126; PrevAnimatorToViewLiteParamsShow0 = (char)(int)ViewAnimatorOnlyNoParams; } if ((char)(int)ViewAnimatorOnlyNoParams != PrevAnimatorToViewLiteParamsShow0) { PrevAnimatorToViewLiteParamsShow0 = (char)127; RuntimeAnimatorController rac = null; allControllers.TryGetValue(ViewAnimatorOnlyNoParams, out rac); updateSelectionDelegate(rac == null ? (UnityEngine.Object)this.emulator : (UnityEngine.Object)rac); } if (RefreshExpressionParams) { RefreshExpressionParams = false; Dictionary stageNameToValue = EarlyRefreshExpressionParameters(); LateRefreshExpressionParameters(stageNameToValue); } if(this == AvatarSyncSource && !IsMirrorClone && !IsShadowClone) { Transform head = animator.GetBoneTransform(HumanBodyBones.Head); if (head != null) { head.localScale = EnableHeadScaling ? new Vector3(0.0001f, 0.0001f, 0.0001f) : new Vector3(1.0f, 1.0f, 1.0f); // head bone is set to 0.0001 locally (not multiplied } } if (DisableMirrorAndShadowClones && (MirrorClone != null || ShadowClone != null)) { allMirrorTransforms = null; allShadowTransforms = null; GameObject.Destroy(MirrorClone.gameObject); MirrorClone = null; GameObject.Destroy(ShadowClone.gameObject); ShadowClone = null; } if (!DisableMirrorAndShadowClones && MirrorClone == null && ShadowClone == null) { CreateMirrorClone(); CreateShadowClone(); } if (emulator != null) { if (LastViewMirrorReflection != ViewMirrorReflection) { emulator.ViewMirrorReflection = ViewMirrorReflection; } else { ViewMirrorReflection = emulator.ViewMirrorReflection; } LastViewMirrorReflection = ViewMirrorReflection; if (LastViewBothRealAndMirror != ViewBothRealAndMirror) { emulator.ViewBothRealAndMirror = ViewBothRealAndMirror; } else { ViewBothRealAndMirror = emulator.ViewBothRealAndMirror; } LastViewBothRealAndMirror = ViewBothRealAndMirror; var osc = emulator.GetComponent(); if (OSCController != null && EnableAvatarOSC && (!osc.openSocket || osc.avatarDescriptor != avadesc)) { EnableAvatarOSC = false; OSCController = null; } if (OSCController != null && !EnableAvatarOSC) { osc.openSocket = false; OSCController = null; } if (OSCController == null && EnableAvatarOSC) { osc = emulator.gameObject.GetOrAddComponent(); osc.openSocket = true; osc.avatarDescriptor = avadesc; osc.enabled = true; OSCController = osc; // updateSelectionDelegate(osc.gameObject); } } if (CreateNonLocalClone) { CreateNonLocalClone = false; GameObject go = GameObject.Instantiate(OriginalSourceClone.gameObject); go.hideFlags = 0; AvatarSyncSource.CloneCount++; go.name = go.name.Substring(0, go.name.Length - 7) + " (Non-Local " + AvatarSyncSource.CloneCount + ")"; go.transform.position = go.transform.position + AvatarSyncSource.CloneCount * new Vector3(0.4f, 0.0f, 0.4f); go.SetActive(true); } if (IsMirrorClone || IsShadowClone) { NonLocalSyncInterval = 0.0f; } else { NonLocalSyncInterval = AvatarSyncSource.NonLocalSyncInterval; } if (nextUpdateTime == 0.0f) { nextUpdateTime = Time.time + NonLocalSyncInterval; } bool ShouldSyncThisFrame = (AvatarSyncSource != this && (Time.time >= nextUpdateTime || NonLocalSyncInterval <= 0.0f)); if (AvatarSyncSource != this) { IKSyncRadialMenu = AvatarSyncSource.IKSyncRadialMenu; LyumaAv3Menu[] menus = AvatarSyncSource.GetComponents(); for (int i = 0; i < Ints.Count; i++) { if (StageParamterToBuiltin.ContainsKey(Ints[i].stageName)) { // Simulate IK sync of open gesture parameter. if (ShouldSyncThisFrame || (IKSyncRadialMenu && menus.Length >= 1 && menus[0].IsControlIKSynced(Ints[i].name)) || (IKSyncRadialMenu && menus.Length >= 2 && menus[1].IsControlIKSynced(Ints[i].name))) { Ints[i].value = ClampByte(AvatarSyncSource.Ints[i].value); } } } for (int i = 0; i < Floats.Count; i++) { if (StageParamterToBuiltin.ContainsKey(Floats[i].stageName)) { // Simulate IK sync of open gesture parameter. if (ShouldSyncThisFrame || (IKSyncRadialMenu && menus.Length >= 1 && menus[0].IsControlIKSynced(Floats[i].name)) || (IKSyncRadialMenu && menus.Length >= 2 && menus[1].IsControlIKSynced(Floats[i].name))) { Floats[i].exportedValue = ClampAndQuantizeFloat(AvatarSyncSource.Floats[i].exportedValue); Floats[i].value = Floats[i].exportedValue; } } } for (int i = 0; i < Bools.Count; i++) { if (StageParamterToBuiltin.ContainsKey(Bools[i].stageName)) { if (ShouldSyncThisFrame) { Bools[i].value = AvatarSyncSource.Bools[i].value; } } } if (ShouldSyncThisFrame) { nextUpdateTime = Time.time + NonLocalSyncInterval; } } if (AvatarSyncSource != this) { // Simulate more continuous "IK sync" of these parameters. VisemeInt = VisemeIdx = AvatarSyncSource.VisemeInt; Viseme = (VisemeIndex)VisemeInt; GestureLeft = AvatarSyncSource.GestureLeft; GestureLeftIdx = AvatarSyncSource.GestureLeftIdx; GestureLeftIdxInt = AvatarSyncSource.GestureLeftIdxInt; GestureLeftWeight = AvatarSyncSource.GestureLeftWeight; GestureRight = AvatarSyncSource.GestureRight; GestureRightIdx = AvatarSyncSource.GestureRightIdx; GestureRightIdxInt = AvatarSyncSource.GestureRightIdxInt; GestureRightWeight = AvatarSyncSource.GestureRightWeight; Velocity = AvatarSyncSource.Velocity; AngularY = AvatarSyncSource.AngularY; Upright = AvatarSyncSource.Upright; Grounded = AvatarSyncSource.Grounded; Seated = AvatarSyncSource.Seated; AFK = AvatarSyncSource.AFK; TrackingType = AvatarSyncSource.TrackingType; TrackingTypeIdx = AvatarSyncSource.TrackingTypeIdx; TrackingTypeIdxInt = AvatarSyncSource.TrackingTypeIdxInt; VRMode = AvatarSyncSource.VRMode; MuteSelf = AvatarSyncSource.MuteSelf; InStation = AvatarSyncSource.InStation; } for (int i = 0; i < Floats.Count; i++) { if (Floats[i].expressionValue != Floats[i].lastExpressionValue_) { Floats[i].exportedValue = Floats[i].expressionValue; Floats[i].lastExpressionValue_ = Floats[i].expressionValue; } if (StageParamterToBuiltin.ContainsKey(Floats[i].stageName)) { if (locally8bitQuantizedFloats) { Floats[i].exportedValue = ClampAndQuantizeFloat(Floats[i].exportedValue); } else { Floats[i].exportedValue = ClampFloatOnly(Floats[i].exportedValue); } Floats[i].value = Floats[i].exportedValue; } } for (int i = 0; i < Ints.Count; i++) { if (StageParamterToBuiltin.ContainsKey(Ints[i].stageName)) { Ints[i].value = ClampByte(Ints[i].value); } } if (Seated != PrevSeated && sittingIndex >= 0 && playableBlendingStates[sittingIndex] != null) { playableBlendingStates[sittingIndex].StartBlend(playableMixer.GetInputWeight(sittingIndex + 1), Seated ? 1f : 0f, 0.25f); PrevSeated = Seated; } if (TPoseCalibration != PrevTPoseCalibration && tposeIndex >= 0 && playableBlendingStates[tposeIndex] != null) { playableBlendingStates[tposeIndex].StartBlend(playableMixer.GetInputWeight(tposeIndex + 1), TPoseCalibration ? 1f : 0f, 0.0f); PrevTPoseCalibration = TPoseCalibration; } if (IKPoseCalibration != PrevIKPoseCalibration && ikposeIndex >= 0 && playableBlendingStates[ikposeIndex] != null) { playableBlendingStates[ikposeIndex].StartBlend(playableMixer.GetInputWeight(ikposeIndex + 1), IKPoseCalibration ? 1f : 0f, 0.0f); PrevIKPoseCalibration = IKPoseCalibration; } if (VisemeIdx != VisemeInt) { VisemeInt = VisemeIdx; Viseme = (VisemeIndex)VisemeInt; } if ((int)Viseme != VisemeInt) { VisemeInt = (int)Viseme; VisemeIdx = VisemeInt; } if (GestureLeftWeight != OldGestureLeftWeight) { OldGestureLeftWeight = GestureLeftWeight; if (GestureLeftWeight < 0.01f) { GestureLeftIdx = 0; } if (GestureLeftWeight > 0.01f && (GestureLeftIdx == 0 || GestureLeftWeight < 0.99f)) { GestureLeftIdx = 1; } } if (GestureRightWeight != OldGestureRightWeight) { OldGestureRightWeight = GestureRightWeight; if (GestureRightWeight < 0.01f) { GestureRightIdx = 0; } if (GestureRightWeight > 0.01f && (GestureRightIdx == 0 || GestureRightWeight < 0.99f)) { GestureRightIdx = 1; } } if (GestureLeftIdx != GestureLeftIdxInt) { GestureLeft = (GestureIndex)GestureLeftIdx; GestureLeftIdx = (int)GestureLeft; GestureLeftIdxInt = (char)GestureLeftIdx; } if ((int)GestureLeft != (int)GestureLeftIdxInt) { GestureLeftIdx = (int)GestureLeft; GestureLeftIdxInt = (char)GestureLeftIdx; } if (GestureRightIdx != GestureRightIdxInt) { GestureRight = (GestureIndex)GestureRightIdx; GestureRightIdx = (int)GestureRight; GestureRightIdxInt = (char)GestureRightIdx; } if ((int)GestureRight != (int)GestureRightIdxInt) { GestureRightIdx = (int)GestureRight; GestureRightIdxInt = (char)GestureRightIdx; } if (GestureLeft == GestureIndex.Neutral) { GestureLeftWeight = 0; } else if (GestureLeft != GestureIndex.Fist) { GestureLeftWeight = 1; } if (GestureRight == GestureIndex.Neutral) { GestureRightWeight = 0; } else if (GestureRight != GestureIndex.Fist) { GestureRightWeight = 1; } if (TrackingTypeIdx != TrackingTypeIdxInt) { TrackingType = (TrackingTypeIndex)TrackingTypeIdx; TrackingTypeIdx = (int)TrackingType; TrackingTypeIdxInt = (char)TrackingTypeIdx; } if ((int)TrackingType != TrackingTypeIdxInt) { TrackingTypeIdx = (int)TrackingType; TrackingTypeIdxInt = (char)TrackingTypeIdx; } IsLocal = AvatarSyncSource == this; int whichcontroller; whichcontroller = 0; foreach (AnimatorControllerPlayable playable in playables) { if (!playable.IsValid()) { whichcontroller++; continue; } // Debug.Log("Index " + whichcontroller + " len " + playables.Count); Dictionary parameterIndices = playableParamterIds[whichcontroller]; int paramid; foreach (FloatParam param in Floats) { if (parameterIndices.TryGetValue(param.name, out paramid)) { if (param.value != param.lastValue) { playable.SetFloat(paramid, param.value); } } } foreach (IntParam param in Ints) { if (parameterIndices.TryGetValue(param.name, out paramid)) { if (param.value != param.lastValue) { playable.SetInteger(paramid, param.value); } } } foreach (BoolParam param in Bools) { if (parameterIndices.TryGetValue(param.name, out paramid)) { if (param.value != param.lastValue) { playable.SetBool(paramid, param.value); // also sets triggers. // if (param.value) { // playable.SetTrigger(paramid); // } } } } whichcontroller++; } foreach (FloatParam param in Floats) { param.lastValue = param.value; } foreach (IntParam param in Ints) { param.lastValue = param.value; } foreach (BoolParam param in Bools) { param.lastValue = param.value; } whichcontroller = 0; foreach (AnimatorControllerPlayable playable in playables) { if (!playable.IsValid()) { whichcontroller++; continue; } // Debug.Log("Index " + whichcontroller + " len " + playables.Count); Dictionary parameterIndices = playableParamterIds[whichcontroller]; Dictionary paramterInts = playableParamterInts[whichcontroller]; Dictionary paramterFloats = playableParamterFloats[whichcontroller]; Dictionary paramterBools = playableParamterBools[whichcontroller]; int paramid; float fparam; int iparam; bool bparam; foreach (FloatParam param in Floats) { if (parameterIndices.TryGetValue(param.name, out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam)) { if (fparam != playable.GetFloat(paramid)) { param.value = param.lastValue = playable.GetFloat(paramid); if (!playable.IsParameterControlledByCurve(paramid)) { param.exportedValue = param.value; } } } paramterFloats[paramid] = param.value; } } foreach (IntParam param in Ints) { if (parameterIndices.TryGetValue(param.name, out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam)) { if (iparam != playable.GetInteger(paramid)) { param.value = param.lastValue = playable.GetInteger(paramid); } } paramterInts[paramid] = param.value; } } foreach (BoolParam param in Bools) { if (param.hasBool[whichcontroller] && parameterIndices.TryGetValue(param.name, out paramid)) { if (paramterBools.TryGetValue(paramid, out bparam)) { if (bparam != (playable.GetBool(paramid))) { param.value = param.lastValue = playable.GetBool(paramid); } } paramterBools[paramid] = param.value; } } if (parameterIndices.TryGetValue("Viseme", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { VisemeInt = VisemeIdx = playable.GetInteger(paramid); Viseme = (VisemeIndex)VisemeInt; } playable.SetInteger(paramid, VisemeInt); paramterInts[paramid] = VisemeInt; } if (parameterIndices.TryGetValue("GestureLeft", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { GestureLeftIdx = playable.GetInteger(paramid); GestureLeftIdxInt = (char)GestureLeftIdx; GestureLeft = (GestureIndex)GestureLeftIdx; } playable.SetInteger(paramid, (int)GestureLeft); paramterInts[paramid] = (int)GestureLeft; } if (parameterIndices.TryGetValue("GestureLeftWeight", out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { GestureLeftWeight = playable.GetFloat(paramid); } playable.SetFloat(paramid, GestureLeftWeight); paramterFloats[paramid] = GestureLeftWeight; } if (parameterIndices.TryGetValue("GestureRight", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { GestureRightIdx = playable.GetInteger(paramid); GestureRightIdxInt = (char)GestureRightIdx; GestureRight = (GestureIndex)GestureRightIdx; } playable.SetInteger(paramid, (int)GestureRight); paramterInts[paramid] = (int)GestureRight; } if (parameterIndices.TryGetValue("GestureRightWeight", out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { GestureRightWeight = playable.GetFloat(paramid); } playable.SetFloat(paramid, GestureRightWeight); paramterFloats[paramid] = GestureRightWeight; } if (parameterIndices.TryGetValue("VelocityX", out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { Velocity.x = playable.GetFloat(paramid); } playable.SetFloat(paramid, Velocity.x); paramterFloats[paramid] = Velocity.x; } if (parameterIndices.TryGetValue("VelocityY", out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { Velocity.y = playable.GetFloat(paramid); } playable.SetFloat(paramid, Velocity.y); paramterFloats[paramid] = Velocity.y; } if (parameterIndices.TryGetValue("VelocityZ", out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { Velocity.z = playable.GetFloat(paramid); } playable.SetFloat(paramid, Velocity.z); paramterFloats[paramid] = Velocity.z; } if (parameterIndices.TryGetValue("AngularY", out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { AngularY = playable.GetFloat(paramid); } playable.SetFloat(paramid, AngularY); paramterFloats[paramid] = AngularY; } if (parameterIndices.TryGetValue("Upright", out paramid)) { if (paramterFloats.TryGetValue(paramid, out fparam) && fparam != playable.GetFloat(paramid)) { Upright = playable.GetFloat(paramid); } playable.SetFloat(paramid, Upright); paramterFloats[paramid] = Upright; } if (parameterIndices.TryGetValue("IsLocal", out paramid)) { playable.SetBool(paramid, IsLocal); } if (parameterIndices.TryGetValue("Grounded", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { Grounded = playable.GetBool(paramid); } playable.SetBool(paramid, Grounded); paramterInts[paramid] = Grounded ? 1 : 0; } if (parameterIndices.TryGetValue("Seated", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { Seated = playable.GetBool(paramid); } playable.SetBool(paramid, Seated); paramterInts[paramid] = Seated ? 1 : 0; } if (parameterIndices.TryGetValue("AFK", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { AFK = playable.GetBool(paramid); } playable.SetBool(paramid, AFK); paramterInts[paramid] = AFK ? 1 : 0; } if (parameterIndices.TryGetValue("TrackingType", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { TrackingTypeIdx = playable.GetInteger(paramid); TrackingTypeIdxInt = (char)TrackingTypeIdx; TrackingType = (TrackingTypeIndex)TrackingTypeIdx; } playable.SetInteger(paramid, (int)TrackingType); paramterInts[paramid] = (int)TrackingType; } if (parameterIndices.TryGetValue("VRMode", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != playable.GetInteger(paramid)) { VRMode = playable.GetInteger(paramid) != 0; } playable.SetInteger(paramid, VRMode ? 1 : 0); paramterInts[paramid] = VRMode ? 1 : 0; } if (parameterIndices.TryGetValue("MuteSelf", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { MuteSelf = playable.GetBool(paramid); } playable.SetBool(paramid, MuteSelf); paramterInts[paramid] = MuteSelf ? 1 : 0; } if (parameterIndices.TryGetValue("InStation", out paramid)) { if (paramterInts.TryGetValue(paramid, out iparam) && iparam != (playable.GetBool(paramid) ? 1 : 0)) { InStation = playable.GetBool(paramid); } playable.SetBool(paramid, InStation); paramterInts[paramid] = InStation ? 1 : 0; } if (parameterIndices.TryGetValue("AvatarVersion", out paramid)) { playable.SetInteger(paramid, AvatarVersion); } whichcontroller++; } if (((emulator != null && !emulator.DisableAvatarDynamicsIntegration) || (AvatarSyncSource?.emulator != null && !AvatarSyncSource.emulator.DisableAvatarDynamicsIntegration)) && !IsMirrorClone && !IsShadowClone) { assignContactParameters(avadesc.gameObject.GetComponentsInChildren()); assignPhysBoneParameters(avadesc.gameObject.GetComponentsInChildren()); } for (int i = 0; i < playableBlendingStates.Count; i++) { var pbs = playableBlendingStates[i]; if (pbs == null) { continue; } if (pbs.blending) { float newWeight = pbs.UpdateBlending(); playableMixer.SetInputWeight(i + 1, newWeight); // Debug.Log("Whole playable " + i + " is blending to " + newWeight); } for (int j = 0; j < pbs.layerBlends.Count; j++) { if (pbs.layerBlends[j].blending) { float newWeight = pbs.layerBlends[j].UpdateBlending(); playables[i].SetLayerWeight(j, newWeight); // Debug.Log("Playable " + i + " layer " + j + " is blending to " + newWeight); } } } if (avadesc.lipSync == VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBone && avadesc.lipSyncJawBone != null) { if (Viseme == VisemeIndex.sil) { avadesc.lipSyncJawBone.transform.rotation = avadesc.lipSyncJawClosed; } else { avadesc.lipSyncJawBone.transform.rotation = avadesc.lipSyncJawOpen; } } else if (avadesc.lipSync == VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape && avadesc.VisemeSkinnedMesh != null && mouthOpenBlendShapeIdx != -1) { if (Viseme == VisemeIndex.sil) { avadesc.VisemeSkinnedMesh.SetBlendShapeWeight(mouthOpenBlendShapeIdx, 0.0f); } else { avadesc.VisemeSkinnedMesh.SetBlendShapeWeight(mouthOpenBlendShapeIdx, 100.0f); } } else if (avadesc.lipSync == VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape && avadesc.VisemeSkinnedMesh != null) { for (int i = 0; i < visemeBlendShapeIdxs.Length; i++) { if (visemeBlendShapeIdxs[i] != -1) { avadesc.VisemeSkinnedMesh.SetBlendShapeWeight(visemeBlendShapeIdxs[i], (i == VisemeIdx ? 100.0f : 0.0f)); } } } } float getObjectFloat(object o) { switch (o) { // case bool b: // return b ? 1.0f : 0.0f; // case int i: // return (float)i; // case long l: // return (float)l; case float f: return f; // case double d: // return (float)d; } return 0.0f; } int getObjectInt(object o) { switch (o) { // case bool b: // return b ? 1 : 0; case int i: return i; // case long l: // return (int)l; // case float f: // return (int)f; // case double d: // return (int)d; } return 0; } bool isObjectTrue(object o) { switch (o) { case bool b: return b; case int i: return i == 1; // case long l: // return l == 1; // case float f: // return f == 1.0f; // case double d: // return d == 1.0; } return false; } public void GetOSCDataInto(List messages) { messages.Add(new A3ESimpleOSC.OSCMessage { arguments = new object[1] {(object)OSCConfigurationFile.OSCAvatarID}, path="/avatar/change", time = new Vector2Int(-1,-1), }); if (OSCConfigurationFile.SendRecvAllParamsNotInJSON) { foreach (var b in Bools) { if (b.synced) { messages.Add(new A3ESimpleOSC.OSCMessage { arguments = new object[1] {(object)(int)((bool)b.value ? 1 : 0)}, path = "/avatar/parameters/" + b.name, time = new Vector2Int(-1,-1), }); } } foreach (var i in Ints) { if (i.synced) { messages.Add(new A3ESimpleOSC.OSCMessage { arguments = new object[1] {(object)(int)i.value}, path = "/avatar/parameters/" + i.name, time = new Vector2Int(-1,-1), }); } } foreach (var f in Floats) { if (f.synced) { messages.Add(new A3ESimpleOSC.OSCMessage { arguments = new object[1] {(object)(float)f.value}, path = "/avatar/parameters/" + f.name, time = new Vector2Int(-1,-1), }); } } } else { foreach (var prop in OSCConfigurationFile.OSCJsonConfig.parameters) { if (prop.name != null && prop.name.Length > 0 && prop.output.address != null && prop.output.address.Length > 0) { string addr = prop.output.address; float outputf = 0.0f; string typ = "?"; if (BoolToIndex.TryGetValue(prop.name, out var bidx)) { if (!Bools[bidx].synced) { continue; } outputf = Bools[bidx].value ? 1.0f : 0.0f; typ = "bool"; } else if (IntToIndex.TryGetValue(prop.name, out var iidx)) { if (!Ints[iidx].synced) { continue; } outputf = (float)Ints[iidx].value; typ = "int"; } else if (FloatToIndex.TryGetValue(prop.name, out var fidx)) { if (!Floats[fidx].synced) { continue; } outputf = Floats[fidx].value; typ = "float"; } else { switch (prop.name) { case "VelocityZ": outputf = Velocity.z; break; case "VelocityY": outputf = Velocity.y; break; case "VelocityX": outputf = Velocity.x; break; case "InStation": outputf = InStation ? 1.0f : 0.0f; break; case "Seated": outputf = Seated ? 1.0f : 0.0f; break; case "AFK": outputf = AFK ? 1.0f : 0.0f; break; case "Upright": outputf = Upright; break; case "AngularY": outputf = AngularY; break; case "Grounded": outputf = Grounded ? 1.0f : 0.0f; break; case "MuteSelf": outputf = MuteSelf ? 1.0f : 0.0f; break; case "VRMode": outputf = VRMode ? 1.0f : 0.0f; break; case "TrackingType": outputf = TrackingTypeIdxInt; break; case "GestureRightWeight": outputf = GestureRightWeight; break; case "GestureRight": outputf = GestureRightIdxInt; break; case "GestureLeftWeight": outputf = GestureLeftWeight; break; case "GestureLeft": outputf = GestureLeftIdxInt; break; case "Voice": outputf = Voice; break; case "Viseme": outputf = VisemeInt; break; default: Debug.LogWarning("Unrecognized built in param"); break; } } object output; switch (prop.output.type) { case "Float": output = (object)(float)outputf; break; case "Int": output = (object)(int)outputf; break; case "Bool": output = (object)(outputf != 0.0f); break; default: Debug.LogError("Unrecognized JSON type " + prop.input.type + " for address " + addr + " for output " + typ + " parameter " + prop.name + ". Should be \"Float\", \"Int\" or \"Bool\"."); continue; } messages.Add(new A3ESimpleOSC.OSCMessage { arguments = new object[1] {(object)output}, path = addr, time = new Vector2Int(-1,-1), }); } } } } public void processOSCInputMessage(string ParamName, object arg0) { float argFloat = getObjectFloat(arg0); int argInt = getObjectInt(arg0); bool argBool = isObjectTrue(arg0); switch (ParamName) { case "Vertical": Velocity.z = (3.0f + RunSpeed) * argFloat; break; case "Horizontal": Velocity.x = (3.0f + RunSpeed) * (float)arg0; break; case "LookHorizontal": AngularY = argFloat; break; case "UseAxisRight": case "GrabAxisRight": case "MoveHoldFB": case "SpinHoldCwCcw": case "SpinHoldUD": case "SpinHoldLR": break; case "MoveForward": Velocity.z = argBool ? 5.0f : 0.0f; break; case "MoveBackward": Velocity.z = argBool ? -5.0f : 0.0f; break; case "MoveLeft": Velocity.x = argBool ? -5.0f : 0.0f; break; case "MoveRight": Velocity.x = argBool ? 5.0f : 0.0f; break; case "LookLeft": AngularY = argBool ? -1.0f : 0.0f; break; case "LookRight": AngularY = argBool ? 1.0f : 0.0f; break; case "Jump": Jump = argBool; break; case "Run": RunSpeed = argBool ? 3.0f : 0.0f; break; case "ComfortLeft": case "ComfortRight": case "DropRight": case "UseRight": case "GrabRight": case "DropLeft": case "UseLeft": case "GrabLeft": case "PanicButton": case "QuickMenuToggleLeft": case "QuickMenuToggleRight": break; case "Voice": if (argBool && !MuteTogglerOn) { MuteSelf = !MuteSelf; } MuteTogglerOn = argBool; break; default: Debug.LogWarning("Unrecognized OSC input command " + ParamName); break; } } public void HandleOSCMessages(List messages) { var innerProperties = new Dictionary(); foreach (var ij in OSCConfigurationFile.OSCJsonConfig.parameters) { if (ij.input.address != null && ij.input.address.Length > 0) { innerProperties[ij.input.address] = ij; } } foreach (var msg in messages) { string msgPath = msg.path; object[] arguments = msg.arguments; if (AvatarSyncSource != this || !IsLocal || IsMirrorClone || IsShadowClone) { return; } float argFloat = getObjectFloat(arguments[0]); int argInt = getObjectInt(arguments[0]); bool argBool = isObjectTrue(arguments[0]); if (msgPath.StartsWith("/input/")) { string ParamName = msgPath.Split(new char[]{'/'}, 3)[2]; processOSCInputMessage(ParamName, arguments[0]); } else { string ParamName; if (OSCConfigurationFile.SendRecvAllParamsNotInJSON) { ParamName = msgPath.Split(new char[]{'/'}, 4)[3]; } else if (innerProperties.ContainsKey(msgPath)) { ParamName = innerProperties[msgPath].name; if (innerProperties[msgPath].input.type == "Float") { if (arguments[0].GetType() != typeof(float)) { Debug.LogWarning("Address " + msgPath + " for parameter " + ParamName + " expected float in JSON but received " + arguments[0].GetType()); continue; } } else if (innerProperties[msgPath].input.type == "Int" || innerProperties[msgPath].input.type == "Bool") { if (arguments[0].GetType() != typeof(int) && arguments[0].GetType() != typeof(bool)) { Debug.LogWarning("Address " + msgPath + " for parameter " + ParamName + " expected int/bool in JSON but received " + arguments[0].GetType()); continue; } } else { Debug.LogError("Unrecognized JSON type " + innerProperties[msgPath].input.type + " for address " + msgPath + " for inupt parameter " + ParamName + " but received " + arguments[0].GetType() + ". Should be \"Float\", \"Int\" or \"Bool\"."); continue; } } else { Debug.LogWarning("Address " + msgPath + " not found for input in JSON."); continue; } if (OSCController != null && OSCController.debugPrintReceivedMessages) { Debug.Log("Recvd "+ParamName + ": " + msg); } if (arguments.Length > 0 && arguments[0].GetType() == typeof(bool)) { int idx; if (BoolToIndex.TryGetValue(ParamName, out idx)) { Bools[idx].value = (bool)(arguments[0]); } if (IntToIndex.TryGetValue(ParamName, out idx)) { Ints[idx].value = (int)(arguments[0]); } } if (arguments.Length > 0 && arguments[0].GetType() == typeof(int)) { int idx; if (BoolToIndex.TryGetValue(ParamName, out idx)) { Bools[idx].value = ((int)(arguments[0])) != 0; } if (IntToIndex.TryGetValue(ParamName, out idx)) { Ints[idx].value = (int)(arguments[0]); } } if (arguments.Length > 0 && arguments[0].GetType() == typeof(float)) { int idx; if (FloatToIndex.TryGetValue(ParamName, out idx)) { Floats[idx].value = (float)(arguments[0]); Floats[idx].exportedValue = Floats[idx].value; } } } } } }