summaryrefslogtreecommitdiff
path: root/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts
diff options
context:
space:
mode:
Diffstat (limited to 'VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts')
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs219
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta11
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs682
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta11
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs51
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta11
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs155
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta11
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs235
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta3
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs375
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta11
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs2529
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta11
14 files changed, 4315 insertions, 0 deletions
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs
new file mode 100644
index 00000000..e278b06c
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs
@@ -0,0 +1,219 @@
+using System;
+using UnityEngine;
+using VRC.SDK3.Avatars.Components;
+using VRC.SDK3.Avatars.ScriptableObjects;
+
+[Serializable]
+public struct A3EOSCConfiguration {
+ public bool UseRealPipelineIdJSONFile;
+ public bool SendRecvAllParamsNotInJSON;
+ public bool GenerateOSCConfig;
+ public bool LoadOSCConfig;
+ public bool SaveOSCConfig;
+ public string OSCAvatarID;
+ public string OSCFilePath;
+ public OuterJson OSCJsonConfig;
+
+ static bool whichtest;
+ [Serializable]
+ public struct InputOutputPath {
+ public string address;
+ public string type;
+ }
+ [Serializable]
+ public struct InnerJson {
+ public string name;
+ public InputOutputPath input;
+ public InputOutputPath output;
+ }
+ [Serializable]
+ public class OuterJson {
+ public string id;
+ public string name;
+ public InnerJson[] parameters;
+ }
+ readonly static string [][] OSC_BUILTIN_PARAMETERS = {
+ new string[]{"VelocityZ","Float"},
+ new string[]{"VelocityY","Float"},
+ new string[]{"VelocityX","Float"},
+ new string[]{"InStation","Bool"},
+ new string[]{"Seated","Bool"},
+ new string[]{"AFK","Bool"},
+ new string[]{"Upright","Float"},
+ new string[]{"AngularY","Float"},
+ new string[]{"Grounded","Bool"},
+ new string[]{"MuteSelf","Bool"},
+ new string[]{"VRMode","Int"},
+ new string[]{"TrackingType","Int"},
+ new string[]{"GestureRightWeight","Float"},
+ new string[]{"GestureRight","Int"},
+ new string[]{"GestureLeftWeight","Float"},
+ new string[]{"GestureLeft","Int"},
+ new string[]{"Voice","Float"},
+ new string[]{"Viseme","Int"}
+ };
+ public static OuterJson GenerateOuterJSON(VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters expparams, string id, string name) {
+ OuterJson oj = new OuterJson();
+ oj.id = id;
+ oj.name = name;
+ const string ADDRESS_PREFIX = "/avatar/parameters/";
+ int nonempty;
+ if (expparams == null) {
+ expparams = ScriptableObject.CreateInstance<VRCExpressionParameters>();
+ expparams.parameters = new VRCExpressionParameters.Parameter[] {
+ new VRCExpressionParameters.Parameter {
+ defaultValue = 0,
+ saved = false,
+ name = "VRCEmote",
+ valueType = VRCExpressionParameters.ValueType.Int
+ },
+ new VRCExpressionParameters.Parameter {
+ defaultValue = 0,
+ saved = false,
+ name = "VRCFaceBlendH",
+ valueType = VRCExpressionParameters.ValueType.Float
+ },
+ new VRCExpressionParameters.Parameter {
+ defaultValue = 0,
+ saved = false,
+ name = "VRCFaceBlendV",
+ valueType = VRCExpressionParameters.ValueType.Float
+ }
+ };
+ }
+ nonempty = expparams.parameters.Length;
+ foreach (var p in expparams.parameters) {
+ if (p.name.Length == 0) {
+ nonempty--;
+ }
+ }
+ oj.parameters = new InnerJson[OSC_BUILTIN_PARAMETERS.Length + nonempty];
+ int idx = 0;
+ // VRC writes these in reverse order. No idea why.
+ foreach (var p in expparams.parameters) {
+ if (p.name.Length != 0) {
+ oj.parameters[nonempty - idx - 1] = new InnerJson {
+ name = p.name,
+ input = new InputOutputPath {
+ address = ADDRESS_PREFIX + p.name,
+ type = (p.valueType == VRCExpressionParameters.ValueType.Int ? "Int" :
+ (p.valueType == VRCExpressionParameters.ValueType.Float ? "Float" : "Bool"))
+ },
+ output = new InputOutputPath {
+ address = ADDRESS_PREFIX + p.name,
+ type = (p.valueType == VRCExpressionParameters.ValueType.Int ? "Int" :
+ (p.valueType == VRCExpressionParameters.ValueType.Float ? "Float" : "Bool"))
+ }
+ };
+ idx++;
+ }
+ }
+ for (int i = 0; i < OSC_BUILTIN_PARAMETERS.Length; i++) {
+ var bname = OSC_BUILTIN_PARAMETERS[i][0];
+ var btype = OSC_BUILTIN_PARAMETERS[i][1];
+ oj.parameters[idx] = new InnerJson {
+ name = bname,
+ output = new InputOutputPath {
+ address = ADDRESS_PREFIX + bname,
+ type = btype
+ }
+ };
+ idx++;
+ }
+ return oj;
+ }
+ public static OuterJson ReadJSON(string full_file_path) {
+ return JsonUtility.FromJson<OuterJson>(System.IO.File.ReadAllText(full_file_path));
+ }
+ public static void WriteJSON(string filename, OuterJson oj) {
+ // pretty print, remove empty {"input":{"address":"","type":""}} junk that unity dumps in, and match VRChat's whitespace.
+ System.IO.File.WriteAllLines(filename, System.Text.RegularExpressions.Regex.Replace("\ufeff" +
+ System.Text.RegularExpressions.Regex.Replace(
+ JsonUtility.ToJson(oj, true), ",\\s*\"input\"\\s*:\\s*{\\s*\"address\"\\s*:\\s*\"\"\\s*,\\s*\"type\"\\s*:\\s*\"\"\\s*}\\s*",""),
+ "\n(\\s*)\\1(\\S)", "\n$1$2").Split('\n'));
+ }
+
+ public const string AVTR_EMULATOR_PREFIX = "avtr_LyumaAv3Emulator_";
+ public void EnsureOSCJSONConfig(VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters expparams, string avatarid, string name) {
+ try {
+ string localLowPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ if (localLowPath.EndsWith("Local")) {
+ localLowPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(localLowPath), "LocalLow");
+ }
+ string userid = null;
+ string vrcOSCPath = System.IO.Path.Combine(localLowPath, "VRChat", "vrchat", "OSC");
+
+ System.Type apiusertype = System.Type.GetType("VRC.Core.APIUser, VRCCore-Editor");
+ if (apiusertype != null) {
+ var idprop = apiusertype.GetProperty("id", System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.Public);
+ var prop = apiusertype.GetProperty("CurrentUser", System.Reflection.BindingFlags.Static|System.Reflection.BindingFlags.Public);
+ // Debug.Log("idprop " + idprop);
+ if (idprop != null && prop != null) {
+ var apiuserinst = prop.GetValue(null);
+ if (apiuserinst != null) {
+ // Debug.Log("apiuser " + apiuserinst);
+ userid = (string)idprop.GetValue(apiuserinst);
+ }
+ }
+ }
+ try {
+ System.IO.Directory.CreateDirectory(vrcOSCPath);
+ } catch (System.IO.IOException) {
+ }
+ if (userid == null || userid.Length == 0) {
+ // do not have a known user account.
+ // find the most recent user folder.
+ DateTime dt = DateTime.MinValue;
+ // Debug.Log("lets-a look at " + vrcOSCPath);
+ foreach (string file in System.IO.Directory.GetDirectories(vrcOSCPath, "*", System.IO.SearchOption.TopDirectoryOnly)) {
+ // Debug.Log("enumerate a file " + file);
+ DateTime thisdt = System.IO.File.GetLastWriteTime(file);
+ if (thisdt > dt) {
+ userid = System.IO.Path.GetFileName(file);
+ dt = thisdt;
+ }
+ }
+ }
+ if (userid == null || userid.Length == 0) {
+ OSCAvatarID = "not_logged_in";
+ OSCFilePath = "No User folder was found. Please play VRC or login.";
+ OSCJsonConfig = GenerateOuterJSON(expparams, "not_logged_in", name);
+ return;
+ }
+ string avatarDirectory = System.IO.Path.Combine(vrcOSCPath, userid, "Avatars");
+ try {
+ System.IO.Directory.CreateDirectory(avatarDirectory);
+ } catch (System.IO.IOException) {
+ }
+ if (avatarid != null && UseRealPipelineIdJSONFile) {
+ OSCAvatarID = avatarid; // json file already exists: let's use it.
+ OSCFilePath = System.IO.Path.Combine(avatarDirectory, avatarid + ".json");
+ } else {
+ avatarid = AVTR_EMULATOR_PREFIX + (whichtest ? "A" : "B");
+ OSCFilePath = System.IO.Path.Combine(avatarDirectory, avatarid + ".json");
+ whichtest = !whichtest;
+ OSCAvatarID = avatarid;
+ WriteJSON(OSCFilePath, GenerateOuterJSON(expparams, avatarid, name));
+ }
+ if (System.IO.File.Exists(OSCFilePath)) {
+ try {
+ OSCJsonConfig = ReadJSON(OSCFilePath);
+ } catch (Exception e) {
+ Debug.LogException(e);
+ Debug.Log("File failed to load. Generating new JSON for " + OSCAvatarID);
+ OSCJsonConfig = GenerateOuterJSON(expparams, OSCAvatarID, name);
+ }
+ } else {
+ Debug.Log("File does not exist. Generating new JSON for " + OSCAvatarID);
+ OSCJsonConfig = GenerateOuterJSON(expparams, OSCAvatarID, name);
+ }
+ } catch (Exception e) {
+ Debug.LogException(e);
+ OSCAvatarID = "exception_generating_json";
+ OSCFilePath = e.Message;
+ Debug.Log("Unable to determine Avatar ID or JSON file path. Generating config.");
+ OSCJsonConfig = GenerateOuterJSON(expparams, "exception_generating_json", name);
+ }
+ }
+
+}
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta
new file mode 100644
index 00000000..2e6245cb
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3EOSCConfiguration.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0a616815e5eee504280da16619284024
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs
new file mode 100644
index 00000000..6b6d8d54
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs
@@ -0,0 +1,682 @@
+/* A3ESimpleOSC for C#, version 0.1
+Copyright (c) 2022 Lyuma <xn.lyuma@gmail.com>
+
+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. */
+
+#if UNITY_5_3_OR_NEWER
+#define UNITY
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+public class A3ESimpleOSC
+{
+ public enum Impulse {IMPULSE}
+ public struct TimeTag {
+ public int secs;
+ public int nsecs;
+ public override string ToString() {
+ return "" + secs + ":" + nsecs;
+ }
+ #if UNITY
+ public static implicit operator UnityEngine.Vector2Int(TimeTag tt) {
+ return new UnityEngine.Vector2Int { x = tt.secs, y = tt.nsecs };
+ }
+ public static implicit operator TimeTag(UnityEngine.Vector2Int v2) {
+ return new TimeTag { secs = v2.x, nsecs = v2.y };
+ }
+ #endif
+ }
+ public struct OSCColor {
+ public byte r;
+ public byte g;
+ public byte b;
+ public byte a;
+ public override string ToString() {
+ return "OSCColor<" + r + "," + g + "," + b + "," + a + ">";
+ }
+ #if UNITY
+ public static implicit operator UnityEngine.Color(OSCColor c) {
+ return (UnityEngine.Color)new UnityEngine.Color32 { r = c.r, g = c.g, b = c.b, a = c.a };
+ }
+ public static implicit operator OSCColor(UnityEngine.Color c) {
+ UnityEngine.Color32 c32 = (UnityEngine.Color32)c;
+ return new OSCColor { r = c32.r, g = c32.g, b = c32.b, a = c32.a };
+ }
+ public static implicit operator OSCColor(UnityEngine.Color32 c) {
+ return new OSCColor { r = c.r, g = c.g, b = c.b, a = c.a };
+ }
+ public static implicit operator UnityEngine.Color32(OSCColor c) {
+ return new UnityEngine.Color32 { r = c.r, g = c.g, b = c.b, a = c.a };
+ }
+ #endif
+ }
+ public struct OSCMessage {
+ public IPEndPoint sender;
+ public uint bundleId; // 0 if not in a bundle; positive integer if part of a bundle.
+ public string path;
+ public TimeTag time;
+ public string typeTag;
+ public object[] arguments;
+ public override string ToString() {
+ System.Text.StringBuilder ret = new System.Text.StringBuilder();
+ ret.Append("<OSCMessage ");
+ DebugInto(ret);
+ ret.Append(">");
+ return ret.ToString();
+ }
+ public void DebugInto(System.Text.StringBuilder ret, bool dispIPTime=true) {
+ ret.Append(path);
+ if (dispIPTime && sender != null) {
+ ret.Append("; from ");
+ ret.Append(sender.ToString());
+ }
+ if (dispIPTime && (time.secs != 0 || time.nsecs != 0)) {
+ ret.Append(" @");
+ ret.Append(time);
+ }
+ if (dispIPTime) {
+ ret.Append("; type ");
+ ret.Append(typeTag);
+ }
+ ret.Append(":\n");
+ DebugObjectArrayInto(ret, " ", arguments);
+ }
+ }
+
+ public static bool DebugLoggingEnabled = true; // Set to false to handle bad data without logspam.
+ static void CryWolf(string logMsg) {
+ if (DebugLoggingEnabled) {
+ #if UNITY
+ UnityEngine.Debug.LogWarning(logMsg);
+ #else
+ System.Console.WriteLine(logMsg);
+ #endif
+ }
+ }
+
+ public static void DebugObjectArrayInto(System.Text.StringBuilder sb, string indent, object[] args) {
+ int idx = 0;
+ foreach (var arg in args) {
+ sb.Append(indent);
+ sb.Append("[Arg");
+ sb.Append(idx);
+ sb.Append("] = ");
+ switch (arg) {
+ case null:
+ sb.Append("null");
+ break;
+ case object[] subArgs:
+ sb.Append("[\n");
+ DebugObjectArrayInto(sb, indent + " ", subArgs);
+ sb.Append(indent + "]");
+ break;
+ case byte[] subBytes:
+ sb.Append("new byte[] {");
+ bool first = true;
+ foreach (byte b in subBytes) {
+ if (!first) {
+ sb.Append(",");
+ }
+ first = false;
+ sb.Append((int)b);
+ }
+ sb.Append("}");
+ break;
+ case float f:
+ sb.Append(f.ToString("F4"));
+ break;
+ case int i:
+ sb.Append(i.ToString());
+ break;
+ case bool b:
+ sb.Append(b.ToString());
+ break;
+ default:
+ sb.Append("(");
+ sb.Append(arg.GetType().Name);
+ sb.Append(")");
+ sb.Append(arg);
+ break;
+ }
+ sb.Append("\n");
+ idx++;
+ }
+ }
+
+
+ /// Parsing / decoding functions:
+ static object ParseType(char typeTag, byte[] data, ref int offset) {
+ switch(typeTag) {
+ case 'T':
+ return true;
+ case 'F':
+ return false;
+ case 'N':
+ return null;
+ case 'I':
+ return Impulse.IMPULSE;
+ case 'i':
+ int iret = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ return iret;
+ case 'f':
+ byte[] tmp = new byte[4];
+ Array.Copy(data, offset, tmp, 0, 4);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(tmp);
+ }
+ float fret = BitConverter.ToSingle(tmp, 0);
+ offset += 4;
+ return fret;
+ case 't':
+ int secs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ int nanosecs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 4));
+ offset += 8;
+ return new TimeTag { secs = secs, nsecs = nanosecs };
+ case 's':
+ int strend = offset;
+ while (data[strend] != 0) {
+ strend++;
+ }
+ tmp = new byte[strend - offset];
+ Array.Copy(data, offset, tmp, 0, strend - offset);
+ offset = strend;
+ offset = (offset + 4) & ~3;
+ return System.Text.Encoding.UTF8.GetString(tmp);
+ case 'b':
+ int len = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ tmp = new byte[len];
+ Array.Copy(data, offset, tmp, 0, len);
+ offset += len;
+ offset = (offset + 3) & ~3;
+ return tmp;
+ // Non-standard types:
+ case 'r':
+ byte r = data[offset++];
+ byte g = data[offset++];
+ byte b = data[offset++];
+ byte a = data[offset++];
+ return new OSCColor { r = r, g = g, b = b, a = a };
+ case 'h':
+ long lret = IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, offset));
+ offset += 8;
+ return lret;
+ case 'd':
+ byte[] dtmp = new byte[8];
+ Array.Copy(data, offset, dtmp, 0, 8);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(dtmp);
+ }
+ double dret = BitConverter.ToDouble(dtmp, 0);
+ offset += 8;
+ return dret;
+ case 'c':
+ uint cret = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ return cret;
+ default:
+ CryWolf("Unknown type tag " + typeTag + " offset " + offset);
+ break;
+ }
+ return null;
+ }
+
+ static void SerializeTypeInto(byte[] data, ref int offset, object value, char typeTag) {
+ // Debug.Log("Serialize " + value.GetType() + " " + (value) + " as " + typeTag);
+ byte[]tmp;
+ switch (typeTag) {
+ case 'T':
+ case 'F':
+ case 'N':
+ case 'I':
+ break;
+ case 'i':
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)value)), 0, data, offset, 4);
+ offset += 4;
+ break;
+ case 'f':
+ tmp = BitConverter.GetBytes((float)value);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(tmp);
+ }
+ Array.Copy(tmp, 0, data, offset, 4);
+ offset += 4;
+ break;
+ case 't':
+ TimeTag v2;
+ switch (value) {
+ #if UNITY
+ case UnityEngine.Vector2Int i2:
+ v2 = (TimeTag)i2;
+ break;
+ #endif
+ default:
+ v2 = (TimeTag)value;
+ break;
+ }
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)v2.secs)), 0, data, offset, 4);
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)v2.nsecs)), 0, data, offset + 4, 4);
+ offset += 8;
+ break;
+ case 's':
+ tmp = System.Text.Encoding.UTF8.GetBytes((string)value);
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ break;
+ case 'b':
+ tmp = (byte[])value;
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tmp.Length)), 0, data, offset, 4);
+ offset += 4;
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 3) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ break;
+ // Non-standard types:
+ case 'r':
+ OSCColor col;
+ switch (value) {
+ #if UNITY
+ case UnityEngine.Color32 unic32:
+ col = (OSCColor)unic32;
+ break;
+ case UnityEngine.Color unic:
+ col = (OSCColor)unic;
+ break;
+ #endif
+ default:
+ col = (OSCColor)value;
+ break;
+ }
+ data[offset++] = col.r;
+ data[offset++] = col.g;
+ data[offset++] = col.b;
+ data[offset++] = col.a;
+ break;
+ case 'h':
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)value)), 0, data, offset, 8);
+ offset += 8;
+ break;
+ case 'd':
+ tmp = BitConverter.GetBytes((double)value);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(tmp);
+ }
+ Array.Copy(tmp, 0, data, offset, 8);
+ offset += 8;
+ break;
+ case 'c':
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)(uint)value)), 0, data, offset, 4);
+ offset += 4;
+ break;
+ default:
+ CryWolf("Unexpected type tag to serialize " + typeTag + " offset " + offset);
+ break;
+ }
+ }
+
+ public static void DecodeOSCInto(ConcurrentQueue<OSCMessage> outQueue, byte[] data, int offset, int length, IPEndPoint senderIp=null, uint bundleId=0, TimeTag bundleTimetag=new TimeTag(), uint bundleIdNested=0) {
+ if (offset == 0 && length > 20 && data[offset] == '#' && data[offset + 1] == 'b' && data[offset + 2] == 'u' && data[offset + 3] == 'n' &&
+ data[offset + 4] == 'd' && data[offset + 5] == 'l' && data[offset + 6] == 'e' && data[offset + 7] == 0) {
+ int secs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 8));
+ int nanosecs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 12));
+ bundleTimetag = new TimeTag { secs = secs, nsecs = nanosecs };
+ offset += 16;
+ while (offset < length) {
+ int msglen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ DecodeOSCInto(outQueue, data, offset, msglen, senderIp, bundleId, bundleTimetag, bundleId);
+ offset += msglen;
+ }
+ return;
+ }
+ OSCMessage msg = new OSCMessage();
+ msg.time = bundleTimetag;
+ msg.sender = senderIp;
+ msg.bundleId = bundleIdNested;
+ int strlen = 0;
+ while (data[offset + strlen] != 0) {
+ strlen++;
+ }
+ msg.path = System.Text.Encoding.UTF8.GetString(data, offset, strlen);
+ offset += strlen;
+ offset = (offset + 4) & ~3;
+ while (data[offset] != ',') {
+ offset++;
+ }
+ int typetags = offset;
+ while (data[offset] != 0) {
+ offset++;
+ }
+ msg.typeTag = System.Text.Encoding.ASCII.GetString(data, typetags, offset - typetags);
+ offset = (offset + 4) & ~3;
+ //msg.arguments = new object[msg.typeTag.Length];
+ List<object> topLevelArguments = new List<object>();
+ List<List<object>> nested = new List<List<object>>();
+ nested.Add(topLevelArguments);
+ for (int i = 1; i < msg.typeTag.Length; i++) {
+ // Debug.Log("doing type tag " + msg.typeTag[i] + " offset: " + offset);
+ object obj;
+ switch (msg.typeTag[i]) {
+ case '[':
+ nested.Add(new List<object>());
+ break;
+ case ']':
+ if (nested.Count > 1) {
+ obj = nested[nested.Count - 1].ToArray();
+ nested.RemoveAt(nested.Count - 1);
+ nested[nested.Count - 1].Add(obj);
+ }
+ break;
+ default:
+ obj = ParseType(msg.typeTag[i], data, ref offset);
+ nested[nested.Count - 1].Add(obj);
+ break;
+ }
+ }
+ if (nested.Count != 1) {
+ CryWolf("Invalid nested count (mismatched start and end array in OSC message): " + msg.typeTag);
+ }
+ msg.arguments = topLevelArguments.ToArray();
+ outQueue.Enqueue(msg);
+ }
+
+ static void GenerateOSCTypeTagInto(System.Text.StringBuilder typeTag, object[] packet) {
+ if (typeTag.Length == 0) {
+ typeTag.Append(',');
+ }
+ foreach (object po in packet) {
+ switch(po) {
+ case object[] subArray:
+ typeTag.Append('[');
+ GenerateOSCTypeTagInto(typeTag, subArray);
+ typeTag.Append(']');
+ break;
+ case float f:
+ typeTag.Append('f');
+ break;
+ case int i:
+ typeTag.Append('i');
+ break;
+ #if UNITY
+ case UnityEngine.Color32 ui32:
+ case UnityEngine.Color ui:
+ #endif
+ case OSCColor i:
+ typeTag.Append('r');
+ break;
+ case true:
+ typeTag.Append('T');
+ break;
+ case false:
+ typeTag.Append('F');
+ break;
+ case null:
+ typeTag.Append('N');
+ break;
+ case Impulse.IMPULSE:
+ typeTag.Append('I');
+ break;
+ case string s:
+ typeTag.Append('s');
+ break;
+ case byte[] b:
+ typeTag.Append('b');
+ break;
+ #if UNITY
+ case UnityEngine.Vector2Int i2:
+ #endif
+ case TimeTag tt:
+ typeTag.Append('t');
+ break;
+ case double d:
+ typeTag.Append('d');
+ break;
+ case long l:
+ typeTag.Append('h');
+ break;
+ case uint c: // represent OSC char as uint. confusing???
+ typeTag.Append('c');
+ break;
+ default:
+ CryWolf("Invalid type " + po.GetType() + " at " + typeTag);
+ break;
+ }
+ }
+ }
+
+ public static void EncodeOSCInto(byte[] data, ref int offset, OSCMessage msg, string type_tag_override="") {
+ if (msg.typeTag == null || msg.typeTag.Length == 0) {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+ GenerateOSCTypeTagInto(sb, msg.arguments);
+ msg.typeTag = sb.ToString();
+ }
+ byte[] tmp = System.Text.Encoding.UTF8.GetBytes((string)msg.path);
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ tmp = System.Text.Encoding.UTF8.GetBytes((string)msg.typeTag);
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ List<object[]> nested = new List<object[]>();
+ nested.Add(msg.arguments);
+ List<int> nestedIdx = new List<int>();
+ nestedIdx.Add(0);
+ foreach (char ch in msg.typeTag) {
+ switch (ch) {
+ case ',':
+ continue;
+ case '[':
+ object[] newArr = (object[])nested[nested.Count-1][nestedIdx[nestedIdx.Count-1]];
+ nested.Add(newArr);
+ nestedIdx[nestedIdx.Count-1] += 1;
+ nestedIdx.Add(0);
+ break;
+ case ']':
+ nested.RemoveAt(nested.Count-1);
+ nestedIdx.RemoveAt(nestedIdx.Count-1);
+ break;
+ default:
+ SerializeTypeInto(data, ref offset, nested[nested.Count-1][nestedIdx[nestedIdx.Count-1]], ch);
+ nestedIdx[nestedIdx.Count-1] += 1;
+ break;
+ }
+ }
+ }
+
+ public static void EncodeOSCBundleInto(byte[] data, ref int offset, List<OSCMessage> packets, TimeTag tt) {
+ Array.Copy(new byte[]{(byte)'#',(byte)'b',(byte)'u',(byte)'n',(byte)'d',(byte)'l',(byte)'e',0}, 0, data, offset, 8);
+ offset += 8;
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tt.secs)), 0, data, offset, 4);
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tt.nsecs)), 0, data, offset + 4, 4);
+ offset += 8;
+ foreach (var msg in packets) {
+ int startOffset = offset;
+ offset += 4;
+ EncodeOSCInto(data, ref offset, msg);
+ int endOffset = offset;
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)(endOffset - startOffset - 4))), 0, data, startOffset, 4);
+ }
+ }
+
+ public class UDPThread {
+ UdpClient udpServer;
+ public bool shutdown;
+ public int udp_port;
+ public uint bundleCounter; // just so callers know if it came from a bundle.
+ public ConcurrentQueue<OSCMessage> receivedMessageQueue = new ConcurrentQueue<OSCMessage>();
+
+ public IPEndPoint Open(int udp_port) {
+ udpServer = new UdpClient(udp_port);
+ return (IPEndPoint)udpServer.Client.LocalEndPoint;
+ }
+ public IPEndPoint Open(IPEndPoint local_udp_endpoint) {
+ udpServer = new UdpClient(local_udp_endpoint);
+ return (IPEndPoint)udpServer.Client.LocalEndPoint;
+ }
+ public void Connect(IPEndPoint endPoint) {
+ udpServer.Connect(endPoint);
+ }
+
+ public void mythread(){
+ while (!shutdown) {
+ var incomingIP = new IPEndPoint(IPAddress.Any, 0);
+ try {
+ var data = udpServer.Receive(ref incomingIP);
+ try {
+ if (bundleCounter == 0) {
+ bundleCounter += 1;
+ }
+ DecodeOSCInto(receivedMessageQueue, data, 0, data.Length, incomingIP, bundleCounter);
+ bundleCounter += 1;
+ } catch (Exception e) {
+ CryWolf(e.ToString());
+ }
+ } catch (SocketException) {
+ if (!shutdown) {
+ continue; //throw;
+ }
+ }
+ }
+ }
+ public void Close() {
+ shutdown = true;
+ if (udpServer != null) {
+ udpServer.Close();
+ }
+ }
+ public void SendBytes(byte[] buffer, int length, IPEndPoint endPoint) {
+ if (endPoint == null) {
+ udpServer.Send(buffer, length);
+ } else {
+ udpServer.Send(buffer, length, endPoint);
+ }
+ }
+ }
+
+ Thread runningThread;
+ UDPThread udpThreadState;
+ byte[]scratchSpace = new byte[8192];
+ IPEndPoint unconnectedEndpoint = null;
+
+ // Two methods for establishing send relationship:
+ // I. A connected UDP socket cannot receive messages from other hosts.
+ public void Connect(IPEndPoint endPoint) {
+ udpThreadState.Connect(endPoint);
+ }
+ // II. This can be called freely for every datagram and only affects data sent.
+ public void SetUnconnectedEndpoint(IPEndPoint endPoint) {
+ unconnectedEndpoint = endPoint;
+ }
+
+ // Two methods for creating a socket (with or without bound local ip/port)
+ // I. Open a socket only. Avoids creating a thread.
+ public IPEndPoint OpenSendOnlyClient(int udp_port=0) {
+ return OpenSendOnlyClient(new IPEndPoint(IPAddress.Any, udp_port));
+ }
+ public IPEndPoint OpenSendOnlyClient(IPEndPoint local_udp_endpoint) {
+ runningThread = null;
+ udpThreadState = new UDPThread();
+ return udpThreadState.Open(local_udp_endpoint);
+ }
+
+ // II. Open a socket, and creates a thread for receiving.
+ public IPEndPoint OpenClient(int udp_port=0) {
+ return OpenClient(new IPEndPoint(IPAddress.Any, udp_port));
+ }
+ public IPEndPoint OpenClient(IPEndPoint local_udp_endpoint) {
+ if (udpThreadState != null) {
+ StopClient();
+ udpThreadState = null;
+ }
+ udpThreadState = new UDPThread();
+ IPEndPoint localEndPoint = udpThreadState.Open(local_udp_endpoint);
+ runningThread = new Thread(new ThreadStart(udpThreadState.mythread));
+ runningThread.Start();
+ return localEndPoint;
+ }
+
+ // Call this to close the socket, and join the thread if any.
+ public void StopClient() {
+ if (udpThreadState != null && !udpThreadState.shutdown) {
+ udpThreadState.Close();
+ if (runningThread != null) {
+ runningThread.Join(5000);
+ }
+ }
+ }
+
+ // Read data waiting in the buffer.
+ public void GetIncomingOSC(List<OSCMessage> incomingMessages) {
+ OSCMessage msg;
+ if (udpThreadState != null && runningThread != null) {
+ while (udpThreadState.receivedMessageQueue.TryDequeue(out msg)) {
+ incomingMessages.Add(msg);
+ }
+ }
+ }
+
+ // Send a single OSCMessage, not bundled.
+ public void SendOSCPacket(OSCMessage msg, byte[] buffer=null) {
+ if (buffer == null) {
+ buffer = scratchSpace;
+ }
+ int encodedLength = 0;
+ EncodeOSCInto(buffer, ref encodedLength, msg);
+ udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint);
+ }
+
+ // Send a single OSCMessage as a bundle.
+ public void SendOSCBundle(List<OSCMessage> messages, TimeTag ts, byte[] buffer=null) {
+ if (messages.Count == 0) {
+ CryWolf("Attempt to send bundle with no messages!");
+ }
+ if (buffer == null) {
+ buffer = scratchSpace;
+ }
+ int encodedLength = 0;
+ EncodeOSCBundleInto(buffer, ref encodedLength, messages, ts);
+ udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint);
+ }
+ public void SendRaw(byte[] buffer, int encodedLength) {
+ udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint);
+ }
+
+ // udpServer.Send(new byte[] { 1 }, 1); // if data is received reply letting the client know that we got his data
+} \ No newline at end of file
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta
new file mode 100644
index 00000000..05af50c7
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 569586a013b244842a857866dc572531
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs
new file mode 100644
index 00000000..b91de425
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs
@@ -0,0 +1,51 @@
+/* Copyright (c) 2020-2022 Lyuma <xn.lyuma@gmail.com>
+
+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 UnityEngine;
+using System.Collections.Generic;
+using VRC.SDK3.Avatars.ScriptableObjects;
+
+public class GestureManagerAv3Menu : LyumaAv3Menu
+{
+
+ // public LyumaAv3Runtime Runtime;
+ // public VRCExpressionsMenu RootMenu;
+ // public bool IsMenuOpen { get; private set; }
+ // private int? _activeControlIndex = null;
+ // private string _activeControlParameterName;
+ public bool compact = true;
+
+ private void Awake()
+ {
+ IsMenuOpen = true;
+
+ if (LyumaAv3Runtime.addRuntimeDelegate != null) {
+ LyumaAv3Runtime.addRuntimeDelegate(this);
+ }
+ }
+
+ // public void ToggleMenu()
+ // {
+
+ // IsMenuOpen = !IsMenuOpen;
+ // }
+
+}
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta
new file mode 100644
index 00000000..2e1b707f
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/GestureManagerAv3Menu.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4cc9dba3b86035b43b8baaca33369f11
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs
new file mode 100644
index 00000000..bb7e9dc3
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs
@@ -0,0 +1,155 @@
+/* Copyright (c) 2020-2022 Lyuma <xn.lyuma@gmail.com>
+
+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 UnityEngine;
+using System.Collections.Generic;
+using VRC.SDK3.Avatars.Components;
+
+[RequireComponent(typeof(Animator))]
+public class LyumaAv3Emulator : MonoBehaviour
+{
+ static readonly ulong EMULATOR_VERSION = 0x2_09_08_00;
+
+ public bool DefaultToVR = false;
+ public bool DefaultTestInStation = false;
+ public LyumaAv3Runtime.TrackingTypeIndex DefaultTrackingType = LyumaAv3Runtime.TrackingTypeIndex.HeadHands;
+ public VRCAvatarDescriptor.AnimLayerType DefaultAnimatorToDebug = VRCAvatarDescriptor.AnimLayerType.Base;
+ public bool RestartEmulator;
+ private bool RestartingEmulator;
+ public bool CreateNonLocalClone;
+ public int CreateNonLocalCloneCount;
+ [Tooltip("Simulate behavior with sub-animator parameter drivers prior to the 2021.1.1 patch (19 Jan 2021)")]
+ public bool legacySubAnimatorParameterDriverMode;
+ public bool legacyMenuGUI = true;
+ private bool lastLegacyMenuGUI = true;
+ public bool DisableAvatarDynamicsIntegration;
+ public bool WorkaroundPlayModeScriptCompile = true;
+ public bool DisableMirrorClone;
+ public bool DisableShadowClone;
+ private bool lastHead;
+ public bool EnableHeadScaling;
+ public bool ViewMirrorReflection;
+ public bool ViewBothRealAndMirror;
+
+ static public LyumaAv3Emulator emulatorInstance;
+ static public RuntimeAnimatorController EmptyController;
+
+ public List<LyumaAv3Runtime> runtimes = new List<LyumaAv3Runtime>();
+
+ private void Awake()
+ {
+ Animator animator = gameObject.GetOrAddComponent<Animator>();
+ animator.enabled = false;
+ animator.runtimeAnimatorController = EmptyController;
+ emulatorInstance = this;
+ VRCAvatarDescriptor[] avatars = FindObjectsOfType<VRCAvatarDescriptor>();
+ Debug.Log(this.name + ": Setting up Av3Emulator on "+avatars.Length + " avatars.", this);
+ foreach (var avadesc in avatars)
+ {
+ if (avadesc.GetComponent<PipelineSaver>() != null) {
+ Debug.Log("Found PipelineSaver on " + avadesc.name + ". Disabling clones and mirror copy.", avadesc);
+ DisableMirrorClone = true;
+ DisableShadowClone = true;
+ CreateNonLocalClone = false;
+ EnableHeadScaling = false;
+ }
+ try {
+ // Creates the playable director, and initializes animator.
+ var oml = avadesc.gameObject.GetOrAddComponent<UnityEngine.AI.OffMeshLink>();
+ oml.startTransform = this.transform;
+ bool alreadyHadComponent = avadesc.gameObject.GetComponent<LyumaAv3Runtime>() != null;
+ var runtime = avadesc.gameObject.GetOrAddComponent<LyumaAv3Runtime>();
+ if (oml != null) {
+ GameObject.DestroyImmediate(oml);
+ }
+ runtime.emulator = this;
+ runtime.VRMode = DefaultToVR;
+ runtime.TrackingType = DefaultTrackingType;
+ runtime.InStation = DefaultTestInStation;
+ runtime.DebugDuplicateAnimator = DefaultAnimatorToDebug;
+ runtime.EnableHeadScaling = EnableHeadScaling;
+ runtimes.Add(runtime);
+ if (!alreadyHadComponent && !DisableShadowClone) {
+ runtime.CreateShadowClone();
+ }
+ if (!alreadyHadComponent && !DisableMirrorClone) {
+ runtime.CreateMirrorClone();
+ }
+ runtime.DisableMirrorAndShadowClones = DisableShadowClone && DisableMirrorClone;
+ } catch (System.Exception e) {
+ Debug.LogException(e);
+ }
+ }
+ if (WorkaroundPlayModeScriptCompile) {
+ LyumaAv3Runtime.ApplyOnEnableWorkaroundDelegate();
+ }
+ }
+
+ private void OnDestroy() {
+ foreach (var runtime in runtimes) {
+ Destroy(runtime);
+ }
+ runtimes.Clear();
+ LyumaAv3Runtime.updateSceneLayersDelegate(~0);
+ }
+
+ private void Update() {
+ if (RestartingEmulator) {
+ RestartingEmulator = false;
+ Awake();
+ } else if (RestartEmulator) {
+ RestartEmulator = false;
+ OnDestroy();
+ RestartingEmulator = true;
+ }
+ if (ViewBothRealAndMirror) {
+ LyumaAv3Runtime.updateSceneLayersDelegate(~0);
+ } else if (ViewMirrorReflection && !ViewBothRealAndMirror) {
+ LyumaAv3Runtime.updateSceneLayersDelegate(~(1<<10));
+ } else if (!ViewMirrorReflection && !ViewBothRealAndMirror) {
+ LyumaAv3Runtime.updateSceneLayersDelegate(~(1<<18));
+ }
+ if (EnableHeadScaling != lastHead) {
+ lastHead = EnableHeadScaling;
+ foreach (var runtime in runtimes) {
+ runtime.EnableHeadScaling = EnableHeadScaling;
+ }
+ }
+ if (lastLegacyMenuGUI != legacyMenuGUI) {
+ lastLegacyMenuGUI = legacyMenuGUI;
+ foreach (var runtime in runtimes) {
+ runtime.legacyMenuGUI = legacyMenuGUI;
+ }
+ }
+ if (CreateNonLocalClone) {
+ CreateNonLocalCloneCount -= 1;
+ if (CreateNonLocalCloneCount <= 0) {
+ CreateNonLocalClone = false;
+ }
+ foreach (var runtime in runtimes)
+ {
+ if (runtime.AvatarSyncSource == runtime)
+ {
+ runtime.CreateNonLocalClone = true;
+ }
+ }
+ }
+ }
+
+}
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta
new file mode 100644
index 00000000..9b3a898b
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Emulator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 226ca8e52c3922d4a85b20831b97caf3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs
new file mode 100644
index 00000000..1fa5c89f
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs
@@ -0,0 +1,235 @@
+/* Copyright (c) 2020-2022 Lyuma <xn.lyuma@gmail.com>
+
+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 UnityEngine;
+using System.Collections.Generic;
+using VRC.SDK3.Avatars.ScriptableObjects;
+
+public class LyumaAv3Menu : MonoBehaviour
+{
+ public bool useLegacyMenu = true;
+ [Serializable]
+ public class MenuConditional
+ {
+ public VRCExpressionsMenu ExpressionsMenu { get; }
+ public LyumaAv3Runtime.FloatParam MandatedParam { get; }
+
+ public MenuConditional(VRCExpressionsMenu expressionsMenu)
+ {
+ ExpressionsMenu = expressionsMenu;
+ }
+
+ public MenuConditional(VRCExpressionsMenu expressionsMenu, LyumaAv3Runtime.FloatParam mandatedParam)
+ {
+ ExpressionsMenu = expressionsMenu;
+ MandatedParam = mandatedParam;
+ }
+
+ bool ShouldMenuRemainOpen(List<LyumaAv3Runtime.FloatParam> allConditions)
+ {
+ if (MandatedParam.name == null) return true;
+
+ var actualParam = allConditions.Find(param => param.name == MandatedParam.name);
+ if (actualParam == null) return false;
+ return actualParam.exportedValue == MandatedParam.exportedValue;
+ }
+ }
+
+ public LyumaAv3Runtime Runtime;
+ public VRCExpressionsMenu RootMenu;
+ public List<MenuConditional> MenuStack { get; } = new List<MenuConditional>();
+ public bool IsMenuOpen { get; protected set; }
+ private int? _activeControlIndex = null;
+ private string _activeControlParameterName;
+
+ private void Awake()
+ {
+ IsMenuOpen = true;
+
+ if (LyumaAv3Runtime.addRuntimeDelegate != null) {
+ LyumaAv3Runtime.addRuntimeDelegate(this);
+ }
+ }
+
+ public void ToggleMenu()
+ {
+ if (IsMenuOpen && _activeControlIndex != null)
+ {
+ UserControlExit();
+ }
+
+ IsMenuOpen = !IsMenuOpen;
+ }
+
+ public void UserToggle(string paramName, float wantedValue) {
+ var intx = Runtime.Ints.Find(param => param.name == paramName);
+ if (intx != null) {
+ var currentValue = intx.value;
+ var newValue = (int)wantedValue == currentValue ? 0 : wantedValue;
+ DoSetRuntimeX(paramName, newValue);
+ }
+ var floatx = Runtime.Floats.Find(param => param.name == paramName);
+ if (floatx != null) {
+ var currentValue = floatx.exportedValue;
+ var newValue = wantedValue == currentValue ? 0.0f : wantedValue;
+ DoSetRuntimeX(paramName, newValue);
+ }
+ var boolx = Runtime.Bools.Find(param => param.name == paramName);
+ if (boolx != null) {
+ var currentValue = boolx.value;
+ var newValue = !currentValue;
+ DoSetRuntimeX(paramName, newValue ? 1.0f : 0.0f);
+ }
+ }
+
+ public void UserSubMenu(VRCExpressionsMenu subMenu)
+ {
+ MenuStack.Add(new MenuConditional(subMenu));
+ }
+
+ public void UserSubMenu(VRCExpressionsMenu subMenu, string paramName, float wantedValue)
+ {
+ MenuStack.Add(new MenuConditional(subMenu, new LyumaAv3Runtime.FloatParam {name = paramName, value = wantedValue, exportedValue = wantedValue}));
+ DoSetRuntimeX(paramName, wantedValue);
+ }
+
+ public void UserBack()
+ {
+ if (MenuStack.Count == 0) return;
+ if (_activeControlIndex != null) return;
+
+ var lastIndex = MenuStack.Count - 1;
+
+ var last = MenuStack[lastIndex];
+ if (last.MandatedParam != null)
+ {
+ DoSetRuntimeX(last.MandatedParam.name, 0.0f);
+ }
+ MenuStack.RemoveAt(lastIndex);
+ }
+
+ public void UserControlEnter(int controlIndex)
+ {
+ if (_activeControlIndex != null) return;
+
+ _activeControlIndex = controlIndex;
+ }
+
+ public void UserControlEnter(int controlIndex, string paramName, float enterValue)
+ {
+ if (_activeControlIndex != null) return;
+
+ _activeControlIndex = controlIndex;
+ _activeControlParameterName = paramName;
+ DoSetRuntimeX(paramName, enterValue);
+ }
+
+ public void UserControlExit()
+ {
+ if (_activeControlIndex == null) return;
+
+ if (_activeControlParameterName != null)
+ {
+ DoSetRuntimeX(_activeControlParameterName, 0.0f);
+ }
+ _activeControlIndex = null;
+ _activeControlParameterName = null;
+ }
+
+ private void DoSetRuntimeX(string paramName, float newValue)
+ {
+ var intParam = Runtime.Ints.Find(param => param.name == paramName);
+ if (intParam != null) {
+ intParam.value = (int)newValue;
+ }
+ var floatParam = Runtime.Floats.Find(param => param.name == paramName);
+ if (floatParam != null) {
+ floatParam.value = newValue;
+ floatParam.exportedValue = newValue;
+ }
+ var boolParam = Runtime.Bools.Find(param => param.name == paramName);
+ if (boolParam != null) {
+ boolParam.value = newValue != 0.0;
+ }
+ }
+
+ public bool IsVisualActive(string paramName, float value)
+ {
+ var intParam = Runtime.Ints.Find(param => param.name == paramName);
+ if (intParam != null) {
+ return intParam.value == (int)value;
+ }
+
+ var floatParam = Runtime.Floats.Find(param => param.name == paramName);
+ if (floatParam != null) {
+ return floatParam.exportedValue == value;
+ }
+
+ var boolParam = Runtime.Bools.Find(param => param.name == paramName);
+ if (boolParam != null) {
+ return boolParam.value == (value != 0.0);
+ }
+ return false;
+ }
+
+ public float FindFloat(string paramName)
+ {
+ var floatParam = Runtime.Floats.Find(param => param.name == paramName);
+ if (floatParam == null) return 0;
+
+ return floatParam.exportedValue;
+ }
+
+ public void UserFloat(string paramName, float newValue)
+ {
+ var floatParam = Runtime.Floats.Find(param => param.name == paramName);
+ if (floatParam == null) return;
+
+ floatParam.value = newValue;
+ floatParam.exportedValue = newValue;
+ }
+
+ public bool HasActiveControl()
+ {
+ return _activeControlIndex != null;
+ }
+
+ public bool IsActiveControl(int controlIndex)
+ {
+ return _activeControlIndex == controlIndex;
+ }
+ public bool IsControlIKSynced(string ParamName) {
+ if (_activeControlIndex == null || !_activeControlIndex.HasValue) {
+ return false;
+ }
+ int idx = _activeControlIndex.Value;
+ var lastStack = MenuStack.Count == 0 ? RootMenu : MenuStack[MenuStack.Count - 1].ExpressionsMenu;
+ if (lastStack != null && lastStack.controls != null && idx < lastStack.controls.Count) {
+ var control = lastStack.controls[idx];
+ foreach (var subp in control.subParameters) {
+ if (subp.name == ParamName) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta
new file mode 100644
index 00000000..178833f5
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Menu.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3865e5f6001a4a9286e8c3f33314c306
+timeCreated: 1604151153 \ No newline at end of file
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs
new file mode 100644
index 00000000..b2d679ae
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs
@@ -0,0 +1,375 @@
+/* Copyright (c) 2020-2022 Lyuma <xn.lyuma@gmail.com>
+
+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 UnityEngine;
+using System.Collections.Generic;
+using System.Linq;
+
+public class LyumaAv3Osc : MonoBehaviour {
+ public delegate Rect GetEditorViewport();
+ public static GetEditorViewport GetEditorViewportDelegate;
+
+ public delegate void DrawDebugRect(Rect pos, Color col, Color outlineCol);
+ public static DrawDebugRect DrawDebugRectDelegate;
+ public delegate void DrawDebugText(Rect contentRect, Color backgroundCol, Color outlineCol, Color textCol, string str, TextAnchor alignment);
+ public static DrawDebugText DrawDebugTextDelegate;
+
+ private LyumaAv3Emulator emulator;
+ A3ESimpleOSC receiver;
+ [Header("OSC Connection")]
+ public bool openSocket = false;
+ public bool disableOSC = false;
+ public bool resendAllParameters = false;
+ byte[] oscBuffer = new byte[65535];
+ public int udpPort = 9000;
+ public string outgoingUdpIp = "127.0.0.1";
+ public int outgoingUdpPort = 9001;
+ [SerializeField] private string commandLine = "";
+ private int oldPort = 9000;
+ [Header("OSC Status")]
+ [SerializeField] private int localPort;
+ [SerializeField] private string localIp;
+ [SerializeField] private int numberOfOSCMessages;
+ [Header("Target Avatar")]
+ public VRC.SDK3.Avatars.Components.VRCAvatarDescriptor avatarDescriptor;
+ public bool forwardToAllAvatarsInScene;
+
+ [Header("Gizmo settings")]
+ public bool alwaysShowOSCGizmos = true;
+ public bool clearGizmos = false;
+ public Color GizmoFilledColor = new Color(1.0f,0.0f,1.0f,0.1f);
+ public Color GizmoBackgroundColor = new Color(0.75f,0.0f,0.6f,0.05f);
+ public Color GizmoOutlineColor = new Color(0.9f,0.7f,0.8f,0.5f);
+ public Color GizmoTextColor = new Color(1.0f,0.8f,1.0f,0.9f); //new Color(0.2f,1.0f,0.5f,1.0f);
+ protected Color GizmoBoundsColor = new Color(0.0f,0.0f,0.0f,0.6f); //new Color(0.2f,1.0f,0.5f,1.0f);
+ public bool GizmoShowSenderIP;
+ [Header("Debug options")]
+ public bool sendLoopbackOSCReplies;
+ public bool debugPrintReceivedMessages;
+
+
+
+ public Dictionary<string, A3ESimpleOSC.OSCMessage> knownPaths = new Dictionary<string, A3ESimpleOSC.OSCMessage>();
+ Dictionary<string, Vector2> minMaxByPath = new Dictionary<string, Vector2>();
+ private List<A3ESimpleOSC.OSCMessage> messages = new List<A3ESimpleOSC.OSCMessage>();
+ Dictionary<string, A3ESimpleOSC.OSCMessage> lastSent = new Dictionary<string, A3ESimpleOSC.OSCMessage>();
+
+ public void Start() {
+ LyumaAv3Emulator[] emulators = FindObjectsOfType<LyumaAv3Emulator>();
+ if (emulators == null || emulators.Length == 0) {
+ return;
+ }
+ emulator = emulators[0];
+ if (emulator != null && emulator.runtimes != null) {
+ if (emulator.runtimes.Count > 0) {
+ avatarDescriptor = emulator.runtimes[0].GetComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>();
+ }
+ }
+ }
+ public void Update() {
+ LyumaAv3Runtime runtime = avatarDescriptor != null ?avatarDescriptor.GetComponent<LyumaAv3Runtime>() : null;
+ commandLine = "--osc=" + udpPort + ":" + outgoingUdpIp + ":" + outgoingUdpPort;
+ if (clearGizmos) {
+ clearGizmos = false;
+ knownPaths.Clear();
+ minMaxByPath.Clear();
+ }
+ if (openSocket && receiver != null && oldPort != udpPort) {
+ receiver.StopClient();
+ receiver = null;
+ }
+ if ((!disableOSC && openSocket) && receiver == null) {
+ localIp = "";
+ localPort = -1;
+ oldPort = udpPort;
+ receiver = new A3ESimpleOSC();
+ bool success = false;
+ try {
+ var localEndpoint = receiver.OpenClient(udpPort);
+ localIp = localEndpoint.Address.ToString();
+ localPort = localEndpoint.Port;
+ success = localEndpoint.Port == udpPort || (udpPort == 0 && localEndpoint.Port > 0);
+ } catch (System.Exception e) {
+ localIp = e.Message;
+ Debug.LogException(e);
+ }
+ if (!success) {
+ Debug.LogError("Failed to bind socket to OSC");
+ openSocket = false;
+ } else {
+ resendAllParameters = true;
+ }
+ }
+ if ((disableOSC||!openSocket) && receiver != null) {
+ receiver.StopClient();
+ receiver = null;
+ }
+ if (resendAllParameters) {
+ resendAllParameters = false;
+ lastSent.Clear();
+ }
+ messages.Clear();
+ if (receiver != null) {
+ receiver.GetIncomingOSC(messages);
+ if (sendLoopbackOSCReplies && messages.Count > 0) {
+ receiver.SetUnconnectedEndpoint(messages[0].sender);
+ var tt = new A3ESimpleOSC.TimeTag();
+ for (int i = 0; i < messages.Count; i++) {
+ if (messages[i].bundleId != 0) {
+ tt = messages[i].time;
+ break;
+ }
+ }
+ receiver.SendOSCBundle(messages, tt);
+ }
+ foreach (var msg in messages) {
+ numberOfOSCMessages += 1;
+ if (debugPrintReceivedMessages) {
+ Debug.Log("Got OSC message: " + msg.ToString());
+ }
+ knownPaths[msg.path] = msg;
+ if (!minMaxByPath.ContainsKey(msg.path)) {
+ minMaxByPath[msg.path] = new Vector2(0,0);
+ }
+ }
+ if (forwardToAllAvatarsInScene) {
+ if (emulator != null && emulator.runtimes != null) {
+ foreach (var instRuntime in emulator.runtimes) {
+ instRuntime.HandleOSCMessages(messages);
+ }
+ }
+ } else if (runtime != null) {
+ runtime.HandleOSCMessages(messages);
+ }
+ }
+ if (runtime != null && receiver != null) {
+ messages.Clear();
+ runtime.GetOSCDataInto(messages);
+ if (messages.Count > 0) {
+ receiver.SetUnconnectedEndpoint(new System.Net.IPEndPoint(System.Net.IPAddress.Parse(outgoingUdpIp), outgoingUdpPort));
+ // receiver.SendOSCBundle(messages, new A3ESimpleOSC.TimeTag { secs=-1, nsecs=-1 }, oscBuffer);
+ foreach (var message in messages) {
+ if (lastSent.ContainsKey(message.path) && Enumerable.SequenceEqual(message.arguments, lastSent[message.path].arguments)) {
+ continue;
+ }
+ lastSent[message.path] = message;
+ if (debugPrintReceivedMessages) {
+ Debug.Log("Sending " + message + " to " + outgoingUdpIp + ":" + outgoingUdpPort);
+ }
+ receiver.SendOSCPacket(message, oscBuffer);
+ }
+ }
+ }
+ if (!enabled && receiver != null) {
+ receiver = null;
+ }
+ }
+ public void OnDestroy() {
+ if (receiver != null) {
+ receiver.StopClient();
+ }
+ }
+
+ Vector3 ScreenToWorld(float x, float y) {
+ Camera camera = Camera.current;
+ Vector3 s = camera.WorldToScreenPoint(transform.position);
+ return camera.ScreenToWorldPoint(new Vector3(x, camera.pixelHeight - y, s.z));
+ }
+
+ Rect ScreenRect(int x, int y, int w, int h) {
+ Vector3 tl = ScreenToWorld(x, y);
+ Vector3 br = ScreenToWorld(x + w, y + h);
+ return new Rect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
+ }
+ void OnDrawGizmos() {
+ if (alwaysShowOSCGizmos) {
+ ActuallyDrawGizmos();
+ }
+ }
+ void RenderBoxType(Rect r, Vector2 v2, Vector2 minMaxHoriz, Vector2 minMaxVert) {
+ float xrange = Mathf.Max(0.001f, minMaxHoriz.y - minMaxHoriz.x);
+ float yrange = Mathf.Max(0.001f, minMaxVert.y - minMaxVert.x);
+ Rect r2 = (Rect)r;
+ Rect r3 = (Rect)r;
+ float f = v2.x;
+ float g = v2.y;
+ float widOffset = r.width * Mathf.Clamp01((v2.x - minMaxHoriz.x) / xrange);
+ float heiOffset = r.height * Mathf.Clamp01(1.0f - (v2.y - minMaxVert.x) / yrange);
+ r.y += heiOffset;
+ r.height -= heiOffset;
+ DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor);
+ r2.width = widOffset;
+ DrawDebugRectDelegate(r2, GizmoFilledColor, GizmoFilledColor);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n\n\n\n"+minMaxHoriz.x.ToString("F2"), TextAnchor.MiddleLeft);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMaxHoriz.y.ToString("F2")+"\n\n\n\n", TextAnchor.MiddleRight);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMaxVert.x.ToString("F2")+"\n", TextAnchor.LowerCenter);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n"+minMaxVert.y.ToString("F2"), TextAnchor.UpperCenter);
+ }
+ void RenderBoxType(Rect r, float f2, Vector2 minMax) {
+ float xrange = Mathf.Max(0.001f, minMax.y - minMax.x);
+ float widOffset = r.width * Mathf.Clamp01(1.0f - (f2 - minMax.x) / xrange);
+ Rect r3 = (Rect)r;
+ r.width -= widOffset;
+ DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n\n\n\n"+minMax.x.ToString("F2"), TextAnchor.MiddleLeft);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMax.y.ToString("F2")+"\n\n\n\n", TextAnchor.MiddleRight);
+ }
+ void RenderBoxType(Rect r, int i2, Vector2Int minMax) {
+ float heiOffset = r.height * Mathf.Clamp01(1.0f - (i2 - minMax.x) / Mathf.Max(1.0f, minMax.y - minMax.x));
+ Rect r3 = (Rect)r;
+ r.y += heiOffset;
+ r.height -= heiOffset;
+ DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, minMax.x.ToString()+"\n", TextAnchor.LowerCenter);
+ DrawDebugTextDelegate(r3, new Color(0,0,0,0), new Color(0,0,0,0), GizmoBoundsColor, "\n"+minMax.y.ToString(), TextAnchor.UpperCenter);
+ }
+ void RenderBoxType(Rect r, bool b2) {
+ if (b2) {
+ DrawDebugRectDelegate(r, GizmoFilledColor, GizmoFilledColor);
+ }
+ }
+
+
+ HashSet<string> usedPartners = new HashSet<string>();
+ Dictionary<string, string> replacePairs = new Dictionary<string, string> {
+ {"Vertical", "Horizontal"},
+ {"Z", "X"},
+ {"Y", "X"},
+ {"z", "x"},
+ {"y", "x"},
+ };
+ void OnDrawGizmosSelected() {
+ if (!alwaysShowOSCGizmos) {
+ ActuallyDrawGizmos();
+ }
+ }
+ void ActuallyDrawGizmos() {
+ Camera camera = Camera.current;
+ Matrix4x4 origMatrix = Gizmos.matrix;
+ Gizmos.matrix = camera.projectionMatrix * camera.transform.localToWorldMatrix;
+ Rect viewportSize = GetEditorViewportDelegate();
+ Rect pos = new Rect(5 + viewportSize.x,5 + viewportSize.y,190,190);
+ usedPartners.Clear();
+ // float maxy = 0;
+ int numBoxes = 0;
+ foreach (var pathPair in knownPaths) {
+ if (usedPartners.Contains(pathPair.Key)) {
+ // Already got output
+ continue;
+ }
+ A3ESimpleOSC.OSCMessage msg;
+ foreach (var replacePair in replacePairs) {
+ if (pathPair.Key.EndsWith(replacePair.Key)) {
+ if (pathPair.Value.arguments.Length >= 1 && pathPair.Value.arguments[0].GetType() == typeof(float)) {
+ string key = pathPair.Key.Substring(0, pathPair.Key.Length - replacePair.Key.Length) + replacePair.Value;
+ if (knownPaths.TryGetValue(key, out msg) && msg.arguments.Length >= 1 && msg.arguments[0].GetType() == typeof(float)) {
+ usedPartners.Add(key);
+ break;
+ }
+ }
+ }
+ }
+ numBoxes++;
+ pos.x += pos.width + 10;
+ if (pos.x + pos.width > viewportSize.width) {
+ pos.x = 5 + viewportSize.x;
+ pos.y += pos.height + 10;
+ }
+ }
+ if (pos.y + pos.height > viewportSize.height) {
+ pos = new Rect(5 + viewportSize.x,5 + viewportSize.y,190,pos.height * viewportSize.height / (pos.y + pos.height + 100));
+ } else {
+ pos = new Rect(5 + viewportSize.x,5 + viewportSize.y,190,190);
+ }
+ foreach (var pathPair in knownPaths) {
+ bool isVec2 = false;
+ Vector2 vecVal = new Vector2();
+ if (usedPartners.Contains(pathPair.Key)) {
+ // Already got output
+ continue;
+ }
+ Vector2 minmaxVert = minMaxByPath[pathPair.Key];
+ Vector2 minmaxHoriz = new Vector2();
+ System.Text.StringBuilder str = new System.Text.StringBuilder();
+ foreach (var replacePair in replacePairs) {
+ if (pathPair.Key.EndsWith(replacePair.Key)) {
+ if (pathPair.Value.arguments.Length >= 1 && pathPair.Value.arguments[0].GetType() == typeof(float)) {
+ string key = pathPair.Key.Substring(0, pathPair.Key.Length - replacePair.Key.Length) + replacePair.Value;
+ A3ESimpleOSC.OSCMessage msg;
+ if (knownPaths.TryGetValue(key, out msg) && msg.arguments.Length >= 1 && msg.arguments[0].GetType() == typeof(float)) {
+ msg.DebugInto(str, GizmoShowSenderIP);
+ vecVal.y = (float)pathPair.Value.arguments[0];
+ minmaxVert = new Vector2(Mathf.Min(minmaxVert.x, vecVal.y), Mathf.Max(minmaxVert.y, vecVal.y));
+ minMaxByPath[pathPair.Key] = minmaxVert;
+
+ vecVal.x = (float)msg.arguments[0];
+ minmaxHoriz = minMaxByPath[key];
+ minmaxHoriz = new Vector2(Mathf.Min(minmaxHoriz.x, vecVal.x), Mathf.Max(minmaxHoriz.y, vecVal.x));
+ minMaxByPath[key] = minmaxHoriz;
+ isVec2 = true;
+ break;
+ }
+ }
+ }
+ }
+ pathPair.Value.DebugInto(str, GizmoShowSenderIP);
+ Rect subPos = new Rect(pos);
+
+ Color bgc = (Color)(GizmoBackgroundColor);
+ if (isVec2) {
+ RenderBoxType(subPos, vecVal, minmaxHoriz, minmaxVert);
+ } else if (pathPair.Value.arguments != null && pathPair.Value.arguments.Length >= 1) {
+ A3ESimpleOSC.OSCMessage msg = pathPair.Value;
+ switch (msg.arguments[0]) {
+ case float f:
+ minmaxVert = new Vector2(Mathf.Min(minmaxVert.x, f), Mathf.Max(minmaxVert.y, f));
+ minMaxByPath[pathPair.Key] = minmaxVert;
+ RenderBoxType(subPos, f, minmaxVert);
+ break;
+ case int i:
+ minmaxVert = new Vector2(Mathf.Min(minmaxVert.x, (float)i), Mathf.Max(minmaxVert.y, (float)i));
+ minMaxByPath[pathPair.Key] = minmaxVert;
+ if (i != 0) {
+ DrawDebugRectDelegate(subPos, GizmoFilledColor, GizmoFilledColor);
+ }
+ RenderBoxType(subPos, i, new Vector2Int(0,255)); // Hardcode bounds. new Vector2Int((int)minmaxVert.x, (int)minmaxVert.y));
+ break;
+ case bool b:
+ if (b) {
+ DrawDebugRectDelegate(subPos, GizmoFilledColor, GizmoFilledColor);
+ }
+ RenderBoxType(subPos, b ? 1 : 0, new Vector2Int(0,1));
+ // RenderBoxType(subPos, b);
+ break;
+ }
+ }
+ // Color c0 = new Color(OUTLINE_COLOR);
+ DrawDebugRectDelegate(pos, bgc, GizmoOutlineColor);
+ DrawDebugTextDelegate(pos, new Color(0.0f,0.0f,1.0f,0.02f), GizmoOutlineColor, GizmoTextColor, str.ToString().Replace(";", "\n").Replace(" @", "\n@" )
+ .Replace("(Single)", "(Float)").Replace("/avatar/parameters/", ""), TextAnchor.MiddleCenter);
+ pos.x += pos.width + 10;
+ if (pos.x + pos.width > viewportSize.width) {
+ pos.x = 5 + viewportSize.x;
+ pos.y += pos.height + 10;
+ }
+ // pos = new Vector3(pos.x + 105, pos.y, pos.z);
+ }
+ Gizmos.matrix = origMatrix;
+ }
+}
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta
new file mode 100644
index 00000000..b3b5946e
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 784aa748a7308b94a8f15dcd76531956
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs
new file mode 100644
index 00000000..04d73cc2
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs
@@ -0,0 +1,2529 @@
+/* Copyright (c) 2020-2022 Lyuma <xn.lyuma@gmail.com>
+
+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<VRCAvatarDescriptor.AnimLayerType, RuntimeAnimatorController> animLayerToDefaultController = new Dictionary<VRCAvatarDescriptor.AnimLayerType, RuntimeAnimatorController>();
+ static public Dictionary<VRCAvatarDescriptor.AnimLayerType, AvatarMask> animLayerToDefaultAvaMask = new Dictionary<VRCAvatarDescriptor.AnimLayerType, AvatarMask>();
+ 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<VRCAvatarDescriptor.AnimLayerType, RuntimeAnimatorController> allControllers = new Dictionary<VRCAvatarDescriptor.AnimLayerType, RuntimeAnimatorController>();
+
+ private Transform[] allTransforms;
+ private Transform[] allMirrorTransforms;
+ private Transform[] allShadowTransforms;
+ private List<AnimatorControllerPlayable> playables = new List<AnimatorControllerPlayable>();
+ private List<Dictionary<string, int>> playableParamterIds = new List<Dictionary<string, int>>();
+ private List<Dictionary<int, float>> playableParamterFloats = new List<Dictionary<int, float>>();
+ private List<Dictionary<int, int>> playableParamterInts = new List<Dictionary<int, int>>();
+ private List<Dictionary<int, bool>> playableParamterBools = new List<Dictionary<int, bool>>();
+ 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<string> BUILTIN_PARAMETERS = new HashSet<string> {
+ "Viseme", "GestureLeft", "GestureLeftWeight", "GestureRight", "GestureRightWeight", "VelocityX", "VelocityY", "VelocityZ", "Upright", "AngularY", "Grounded", "Seated", "AFK", "TrackingType", "VRMode", "MuteSelf", "InStation"
+ };
+ public static readonly HashSet<Type> MirrorCloneComponentBlacklist = new HashSet<Type> {
+ typeof(Camera), typeof(FlareLayer), typeof(AudioSource), typeof(Rigidbody), typeof(Joint)
+ };
+ public static readonly HashSet<Type> ShadowCloneComponentBlacklist = new HashSet<Type> {
+ 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<FloatParam> Floats = new List<FloatParam>();
+ public Dictionary<string, int> FloatToIndex = new Dictionary<string, int>();
+
+ [Serializable]
+ public class IntParam
+ {
+ [HideInInspector] public string stageName;
+ public string name;
+ [HideInInspector] public bool synced;
+ public int value;
+ [HideInInspector] public int lastValue;
+ }
+ public List<IntParam> Ints = new List<IntParam>();
+ public Dictionary<string, int> IntToIndex = new Dictionary<string, int>();
+
+ [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<BoolParam> Bools = new List<BoolParam>();
+ public Dictionary<string, int> BoolToIndex = new Dictionary<string, int>();
+
+ public Dictionary<string, string> StageParamterToBuiltin = new Dictionary<string, string>();
+
+ [HideInInspector] public LyumaAv3Emulator emulator;
+
+ static public Dictionary<Animator, LyumaAv3Runtime> animatorToTopLevelRuntime = new Dictionary<Animator, LyumaAv3Runtime>();
+ private HashSet<Animator> attachedAnimators;
+ private HashSet<string> duplicateParameterAdds = new HashSet<string>();
+
+ 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<BlendingState> layerBlends = new List<BlendingState>();
+
+ }
+ List<PlayableBlendingState> playableBlendingStates = new List<PlayableBlendingState>();
+
+ static HashSet<Animator> issuedWarningAnimators = new HashSet<Animator>();
+ 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<LyumaAv3Runtime>();
+ 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<string> newParameterAdds = new HashSet<string>();
+ HashSet<string> deleteParameterAdds = new HashSet<string>();
+ 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<Animator>();
+ if (AvatarSyncSource == null) {
+ var oml = GetComponent<UnityEngine.AI.OffMeshLink>();
+ if (oml != null && oml.startTransform != null) {
+ this.emulator = oml.startTransform.GetComponent<LyumaAv3Emulator>();
+ 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<LyumaAv3Runtime>();
+ }
+
+ if (this.emulator != null) {
+ DebugDuplicateAnimator = this.emulator.DefaultAnimatorToDebug;
+ ViewAnimatorOnlyNoParams = this.emulator.DefaultAnimatorToDebug;
+ }
+
+ animator = this.gameObject.GetOrAddComponent<Animator>();
+ 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<VRCAvatarDescriptor>();
+ 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<PipelineSaver>() == null) {
+ GameObject cloned = GameObject.Instantiate(gameObject);
+ cloned.hideFlags = HideFlags.HideAndDontSave;
+ cloned.SetActive(false);
+ OriginalSourceClone = cloned.GetComponent<LyumaAv3Runtime>();
+ Debug.Log("Spawned a hidden source clone " + OriginalSourceClone, OriginalSourceClone);
+ OriginalSourceClone.OriginalSourceClone = OriginalSourceClone;
+ }
+ foreach (var smr in gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(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<Transform>(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<VRC.Core.PipelineManager>();
+ string avatarid = pipelineManager != null ? pipelineManager.blueprintId : null;
+ OSCConfigurationFile.EnsureOSCJSONConfig(avadesc.expressionParameters, avatarid, this.gameObject.name);
+ }
+ }
+
+ public void CreateMirrorClone() {
+ if (AvatarSyncSource == this && GetComponent<PipelineSaver>() == null) {
+ OriginalSourceClone.IsMirrorClone = true;
+ MirrorClone = GameObject.Instantiate(OriginalSourceClone.gameObject).GetComponent<LyumaAv3Runtime>();
+ MirrorClone.GetComponent<Animator>().avatar = null;
+ OriginalSourceClone.IsMirrorClone = false;
+ GameObject o = MirrorClone.gameObject;
+ o.name = gameObject.name + " (MirrorReflection)";
+ o.SetActive(true);
+ allMirrorTransforms = MirrorClone.gameObject.GetComponentsInChildren<Transform>(true);
+ foreach (Component component in MirrorClone.gameObject.GetComponentsInChildren<Component>(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<PipelineSaver>() == null) {
+ OriginalSourceClone.IsShadowClone = true;
+ ShadowClone = GameObject.Instantiate(OriginalSourceClone.gameObject).GetComponent<LyumaAv3Runtime>();
+ ShadowClone.GetComponent<Animator>().avatar = null;
+ OriginalSourceClone.IsShadowClone = false;
+ GameObject o = ShadowClone.gameObject;
+ o.name = gameObject.name + " (ShadowClone)";
+ o.SetActive(true);
+ allShadowTransforms = ShadowClone.gameObject.GetComponentsInChildren<Transform>(true);
+ foreach (Component component in ShadowClone.gameObject.GetComponentsInChildren<Component>(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<Renderer>(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>();
+ 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<VRCAvatarDescriptor>();
+ 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<VRCAvatarDescriptor.CustomAnimLayer> allLayers = new List<VRCAvatarDescriptor.CustomAnimLayer>();
+ // 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<Animator>(true);
+ foreach (Animator anim in animators)
+ {
+ attachedAnimators.Add(anim);
+ animatorToTopLevelRuntime.Add(anim, this);
+ }
+
+ Dictionary<string, float> stageNameToValue = EarlyRefreshExpressionParameters();
+ if (animator.playableGraph.IsValid())
+ {
+ animator.playableGraph.Destroy();
+ }
+ // var director = avadesc.gameObject.GetComponent<PlayableDirector>();
+ 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<string, float> EarlyRefreshExpressionParameters() {
+ Dictionary<string, float> stageNameToValue = new Dictionary<string, float>();
+ 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<string, float> stageNameToValue) {
+ HashSet<string> usedparams = new HashSet<string>(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<string, int> parameterIndices = new Dictionary<string, int>();
+ playableParamterInts.Add(new Dictionary<int, int>());
+ playableParamterFloats.Add(new Dictionary<int, float>());
+ playableParamterBools.Add(new Dictionary<int, bool>());
+ // 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<LyumaAv3Menu>()) {
+ UnityEngine.Object.Destroy(comp);
+ }
+ LyumaAv3Menu mainMenu;
+ if (gestureManagerMenu != null) {
+ mainMenu = (LyumaAv3Menu)avadesc.gameObject.AddComponent(gestureManagerMenu);
+ mainMenu.useLegacyMenu = legacyMenuGUI;
+ } else {
+ mainMenu = avadesc.gameObject.AddComponent<LyumaAv3Menu>();
+ }
+ 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>();
+ MeshRenderer xmr = allXTransforms[i].GetComponent<MeshRenderer>();
+ 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<VRC.Core.PipelineManager>();
+ 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<LyumaAv3Menu>()) {
+ 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<string, float> 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<LyumaAv3Osc>();
+ 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<LyumaAv3Osc>();
+ 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<LyumaAv3Menu>();
+ 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<string, int> 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<string, int> parameterIndices = playableParamterIds[whichcontroller];
+ Dictionary<int, int> paramterInts = playableParamterInts[whichcontroller];
+ Dictionary<int, float> paramterFloats = playableParamterFloats[whichcontroller];
+ Dictionary<int, bool> 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<VRCContactReceiver>());
+ assignPhysBoneParameters(avadesc.gameObject.GetComponentsInChildren<VRCPhysBone>());
+ }
+
+ 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<A3ESimpleOSC.OSCMessage> 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<A3ESimpleOSC.OSCMessage> messages) {
+ var innerProperties = new Dictionary<string, A3EOSCConfiguration.InnerJson>();
+ 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;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta
new file mode 100644
index 00000000..fe086a2b
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Runtime.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: da29383b5c207b04585f808c1caad277
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: