diff options
Diffstat (limited to 'VRCSDK3AvatarsQuest/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs')
-rw-r--r-- | VRCSDK3AvatarsQuest/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/VRCSDK3AvatarsQuest/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs b/VRCSDK3AvatarsQuest/Assets/Resources/Lyuma/Av3Emulator/Scripts/LyumaAv3Osc.cs new file mode 100644 index 00000000..b2d679ae --- /dev/null +++ b/VRCSDK3AvatarsQuest/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; + } +} |