#if UNITY_5_3_OR_NEWER using System.Collections; using UnityEngine; using UnityEngine.Networking; #if GLTF using GLTFast; #endif namespace RoboidControl.Unity { /// /// The Unity representation fo a Roboid Control Thing /// public class Thing : MonoBehaviour { /// /// The core C# thing /// public RoboidControl.Thing core { get; set; } /// /// The owner of this thing /// public Participant owner; /// /// Create a Unity representation of a Thing /// /// The core of the thing /// The created thing public static Thing Create(RoboidControl.Thing core) { Debug.Log("Creating new Unity thing"); GameObject gameObj = string.IsNullOrEmpty(core.name) ? new("Thing") : new(core.name); Thing component = gameObj.AddComponent(); component.Init(core); return component; } /// /// Initialize the Thing /// /// The core of the thing /// This affects the parent and pose of the thing protected void Init(RoboidControl.Thing core) { this.core = core; this.core.component = this; // This is wrong, it should get the owner, which is not the siteserver // this.owner = FindAnyObjectByType(); // core.owner = this.owner.coreParticipant; this.owner = core.owner.component; if (core.parent != null && core.parent.component != null) { this.transform.SetParent(core.parent.component.transform, false); this.transform.localPosition = Vector3.zero; } else if (core.owner.component != null) { this.transform.SetParent(core.owner.component.transform, false); } if (core.position != null) this.transform.localPosition = core.position.ToVector3(); if (core.orientation != null) this.transform.localRotation = core.orientation.ToQuaternion(); } /// /// Update the Unity rendering /// protected virtual void Update() { if (core == null) return; if (core.linearVelocity != null && core.linearVelocity.distance != 0) { Vector3 direction = Quaternion.AngleAxis(core.linearVelocity.direction.horizontal, Vector3.up) * Vector3.forward; this.transform.Translate(core.linearVelocity.distance * Time.deltaTime * direction, Space.Self); } if (core.angularVelocity != null && core.angularVelocity.distance != 0) { Vector3 axis = core.angularVelocity.direction.ToVector3(); this.transform.localRotation *= Quaternion.AngleAxis(core.angularVelocity.distance * Time.deltaTime, axis); } } /// /// Update the Unity state (just calls UpdateThing) /// protected virtual void FixedUpdate() { UpdateThing(); // if (core != null) { // // This is new.... // core.orientation = LinearAlgebra.SwingTwist.FromQuaternion(this.transform.localRotation); // } } /// /// Update the Unity state /// public void UpdateThing() { if (core == null) { // Debug.Log($"{this} core thing is gone, self destruct in 0 seconds..."); // Destroy(this); return; } if (core.updateQueue == null) return; while (core.updateQueue.TryDequeue(out RoboidControl.Thing.CoreEvent e)) HandleCoreEvent(e); } /// /// Handle events from the core thing /// /// The core event to handle private void HandleCoreEvent(RoboidControl.Thing.CoreEvent coreEvent) { switch (coreEvent.messageId) { case ThingMsg.id: Debug.Log($"{this.core.id} Handle Thing"); if (core.parent == null) this.transform.SetParent(core.owner.component.transform, true); else if (core.parent.component != null) this.transform.SetParent(core.parent.component.transform, true); break; case NameMsg.Id: Debug.Log($"{this.core.id} Handle Name"); this.gameObject.name = core.name; break; case ModelUrlMsg.Id: Debug.Log("{this.id} Handle Model URL"); string extension = core.modelUrl[core.modelUrl.LastIndexOf(".")..]; if (extension == ".jpg" || extension == ".png") StartCoroutine(LoadJPG()); #if GLTF else if (extension == ".gltf" || extension == ".glb") ProcessGltfModel(core); #endif break; case PoseMsg.Id: Debug.Log($"{this.core.id} Handle Pose"); this.HandlePose(); break; case BinaryMsg.Id: Debug.Log($"{this.core.id} Handle Binary"); this.HandleBinary(); break; } } /// /// Load and attach a JPG sprite visualization of the thing /// /// private IEnumerator LoadJPG() { UnityWebRequest request = UnityWebRequestTexture.GetTexture(core.modelUrl); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { Texture2D texture = ((DownloadHandlerTexture)request.downloadHandler).texture; float aspectRatio = (float)texture.width / (float)texture.height; GameObject modelQuad = GameObject.CreatePrimitive(PrimitiveType.Quad); Collider c = modelQuad.GetComponent(); c.enabled = false; Destroy(c); modelQuad.transform.SetParent(this.transform, false); modelQuad.transform.localEulerAngles = new(90, -90, 0); modelQuad.transform.localScale = new Vector3(aspectRatio, 1, 1) / 5; if (this.name == "Ant") modelQuad.transform.localScale *= 2; Material quadMaterial = new(Shader.Find("Unlit/Transparent")) { mainTexture = texture }; modelQuad.GetComponent().material = quadMaterial; } else { Debug.LogError("Failed to load image: " + request.error); } } #if GLTF bool loadingModel = false; private async void ProcessGltfModel(RoboidControl.Thing coreThing) { if (!loadingModel) { loadingModel = true; Debug.Log("Loading GLTF model from :" + coreThing.modelUrl); GltfImport gltfImport = new GltfImport(); bool success = await gltfImport.Load(coreThing.modelUrl); if (success) { Transform parentTransform = this.transform; Thing[] things = FindObjectsOfType(); Thing parentThing = null; foreach (Thing thing in things) { if (thing.core.owner.networkId == coreThing.owner.networkId && thing.core.id == coreThing.id) { parentTransform = thing.transform; parentThing = thing; } } await gltfImport.InstantiateMainSceneAsync(parentTransform); SkinnedMeshRenderer[] meshRenderers = parentTransform.GetComponentsInChildren(); parentTransform.localScale = Vector3.one; if (meshRenderers.Length > 0) { foreach (SkinnedMeshRenderer meshRenderer in meshRenderers) { if (meshRenderer.rootBone != null) { Debug.Log("Found a skinned mesh with bones"); ScanForThings(meshRenderer.rootBone); break; } } } else ScanForThings(parentTransform); } else { this.transform.localScale = Vector3.one * 1; } } loadingModel = true; } #endif private void ScanForThings(Transform rootTransform) { RoboidControl.Thing[] thingArray = this.core.owner.things.ToArray(); for (int thingIx = 0; thingIx < thingArray.Length; thingIx++) { RoboidControl.Thing thing = thingArray[thingIx]; GameObject foundObj = FindThingByName(thing, rootTransform); if (foundObj != null && (thing.component != null && foundObj != thing.component.gameObject)) { Thing foundThing = foundObj.GetComponent(); if (foundThing == null) { Debug.Log($"move thing [{thing.owner.networkId}/{thing.id}] to {foundObj.name}"); foundThing = foundObj.AddComponent(); foundThing.core = thing; foundThing.core.position = LinearAlgebra.Spherical.FromVector3(foundObj.transform.localPosition); foundThing.core.orientation = LinearAlgebra.SwingTwist.FromQuaternion(foundObj.transform.localRotation); Destroy(thing.component.gameObject); thing.component = foundThing; } else { Debug.LogWarning($"Could not find [{thing.owner.networkId}/{thing.id}]"); } } } } private GameObject FindThingByName(RoboidControl.Thing thing, Transform rootTransform) { if (rootTransform == null || thing == null) return null; if (rootTransform.name == thing.name) return rootTransform.gameObject; for (int childIx = 0; childIx < rootTransform.childCount; childIx++) { Transform child = rootTransform.GetChild(childIx); GameObject foundObj = FindThingByName(thing, child); if (foundObj != null) return foundObj; } return null; } /// /// Handle a Pose event /// /// This can update the position and/or orientation when the velocity of the thing is zero. /// If a velocity is not zero, the position and/or orientation update will be ignored protected virtual void HandlePose() { this.transform.localRotation = core.orientation.ToQuaternion(); this.transform.localPosition = core.position.ToVector3(); // if (core.linearVelocity.distance == 0) // this.transform.localPosition = core.position.ToVector3(); // if (core.angularVelocity.distance == 0) // this.transform.localRotation = core.orientation.ToQuaternion(); } /// /// Handle a Binary event /// protected virtual void HandleBinary() { } } } #endif