301 lines
12 KiB
C#

#if UNITY_5_3_OR_NEWER
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
#if GLTF
using GLTFast;
#endif
namespace RoboidControl.Unity {
/// <summary>
/// The Unity representation fo a Roboid Control Thing
/// </summary>
public class Thing : MonoBehaviour {
/// <summary>
/// The core C# thing
/// </summary>
public RoboidControl.Thing core { get; set; }
/// <summary>
/// The owner of this thing
/// </summary>
public Participant owner;
/// <summary>
/// Create a Unity representation of a Thing
/// </summary>
/// <param name="core">The core of the thing</param>
/// <returns>The created thing</returns>
public static Thing Create(RoboidControl.Thing core) {
GameObject gameObj = string.IsNullOrEmpty(core.name) ?
new("Thing") :
new(core.name);
Thing component = gameObj.AddComponent<Thing>();
component.Init(core);
return component;
}
/// <summary>
/// Initialize the Thing
/// </summary>
/// <param name="core">The core of the thing</param>
/// This affects the parent and pose of the thing
public 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<SiteServer>();
// 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();
}
/// <summary>
/// Update the Unity rendering
/// </summary>
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);
// core.position = LinearAlgebra.Spherical.FromVector3(this.transform.localPosition);
}
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);
// core.orientation = LinearAlgebra.SwingTwist.FromQuaternion(this.transform.localRotation);
}
}
/// <summary>
/// Update the Unity state (just calls UpdateThing)
/// </summary>
protected virtual void FixedUpdate() {
UpdateThing();
// if (core != null) {
// // This is new....
// core.orientation = LinearAlgebra.SwingTwist.FromQuaternion(this.transform.localRotation);
// }
}
/// <summary>
/// Update the Unity state
/// </summary>
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);
}
/// <summary>
/// Handle events from the core thing
/// </summary>
/// <param name="coreEvent">The core event to handle</param>
private void HandleCoreEvent(RoboidControl.Thing.CoreEvent coreEvent) {
switch (coreEvent.messageId) {
case ThingMsg.id:
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:
this.gameObject.name = core.name;
break;
case ModelUrlMsg.Id:
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:
this.HandlePose();
break;
case BinaryMsg.Id:
this.HandleBinary();
break;
}
}
/// <summary>
/// Load and attach a JPG sprite visualization of the thing
/// </summary>
/// <returns></returns>
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<Collider>();
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<Renderer>().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;
#if DEBUG
Debug.Log("Loading GLTF model from :" + coreThing.modelUrl);
#endif
GltfImport gltfImport = new GltfImport();
bool success = await gltfImport.Load(coreThing.modelUrl);
if (success) {
Transform parentTransform = this.transform;
Thing[] things = FindObjectsOfType<Thing>();
Thing parentThing = null;
foreach (Thing thing in things) {
if (thing.core != null && 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<SkinnedMeshRenderer>();
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);
AddMeshColliders(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<Thing>();
if (foundThing == null) {
// Debug.Log($"move thing [{thing.owner.networkId}/{thing.id}] to {foundObj.name}");
foundThing = foundObj.AddComponent<Thing>();
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;
}
private void AddMeshColliders(Transform rootTransform) {
MeshRenderer[] meshRenderers = rootTransform.GetComponentsInChildren<MeshRenderer>();
foreach (MeshRenderer meshRenderer in meshRenderers) {
MeshFilter meshFilter = meshRenderer.GetComponent<MeshFilter>();
MeshCollider meshCollider = meshRenderer.gameObject.AddComponent<MeshCollider>();
meshCollider.sharedMesh = meshFilter.sharedMesh;
meshCollider.convex = true;
}
}
/// <summary>
/// Handle a Pose event
/// </summary>
/// 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();
}
/// <summary>
/// Handle a Binary event
/// </summary>
protected virtual void HandleBinary() { }
}
}
#endif