using System; using System.Collections.Generic; using System.Collections.Concurrent; using LinearAlgebra; namespace RoboidControl { /// /// A thing is the primitive building block /// [Serializable] public class Thing { /// /// Predefined thing types /// public static class Type { public const byte Undetermined = 0x00; // sensor public const byte Switch = 0x01; public const byte DistanceSensor = 0x02; public const byte DirectionalSensor = 0x03; public const byte TemperatureSensor = 0x04; public const byte TouchSensor = 0x05; // Motor public const byte ControlledMotor = 0x06; public const byte UncontrolledMotor = 0x07; public const byte Servo = 0x08; public const byte RelativeEncoder = 0x19; // Other public const byte Root = 0x10; public const byte Roboid = 0x09; public const byte Humanoid = 0x0A; public const byte ExternalSensor = 0x0B; public const byte Animator = 0x0C; public const byte DifferentialDrive = 0x0D; } #region Init /// /// Create a new Thing /// /// (optional) The parent thing /// The owner will be the same as the owner of the parent thing, it will /// be Participant.LocalParticipant if the parent is not specified. A thing /// without a parent will be connected to the root thing. /// public Thing(Thing parent = default) { this.type = Type.Undetermined; this.positionUpdated = true; this.orientationUpdated = true; this.hierarchyChanged = true; if (parent == default) this.parent = Participant.localParticipant.root; else this.parent = parent; this.owner = this.parent.owner; this.owner.Add(this, true); Participant.UpdateEvent e = new() { messageId = ThingMsg.id, thing = this }; this.owner.updateQueue.Enqueue(e); } /// /// Constructor to create a root thing /// /// The participant who will own this root thing /// This function is private because CreateRoot() should be used instead private Thing(Participant owner) { this.type = Type.Root; this.name = "Root"; this.positionUpdated = true; this.orientationUpdated = true; this.hierarchyChanged = true; this.owner = owner; this.parent = null; this.owner.Add(this); this.id = 0; // Root always has id 0 } /// /// Create a root Thing for a participant /// /// The participant who will own this root thing public static void CreateRoot(Participant owner) { owner.root = new Thing(owner); } /// /// The root thing for the local participant /// public static Thing localRoot { get { Participant participant = Participant.localParticipant; return participant.root; } } /// /// Function which can be used to create components in external engines. /// /// Currently this is used to create GameObjects in Unity public virtual void CreateComponent() { #if UNITY_5_3_OR_NEWER this.component = Unity.Thing.Create(this); this.component.core = this; #endif } #endregion Init /// /// Terminated things are no longer updated /// public bool terminate = false; #region Properties /// /// The ID of this thing /// public byte id = 0; /// /// The type of this thing. /// /// This can be either a \ref RoboidControl::Thing::Type "Thing.Type" or a byte value for custom types. public byte type = Type.Undetermined; /// /// The participant owning this thing /// public Participant owner = null; private string _name = ""; /// /// The name of the thing /// public virtual string name { get => _name; set { if (_name != value) { _name = value; nameChanged = true; this.updateQueue.Enqueue(new CoreEvent(NameMsg.Id)); } } } public bool nameChanged = false; private string _modelUrl = ""; /// /// An URL pointing to the location where a model of the thing can be found /// /// Although the roboid implementation is not dependent on the model, /// the only official supported model formats are .png (sprite), .gltf and .glb /// public string modelUrl { get => _modelUrl; set { if (value != _modelUrl) { _modelUrl = value; this.updateQueue.Enqueue(new CoreEvent(ModelUrlMsg.Id)); } } } #if UNITY_5_3_OR_NEWER /// /// A reference to the representation of the thing in Unity /// [NonSerialized] public Unity.Thing component = null; #endif #endregion Properties #region Hierarchy private Thing _parent; /// /// The parent of this thing /// public Thing parent { get => _parent; set { if (_parent == value) return; if (value == null) { _parent?.RemoveChild(this); _parent = null; } else { value.AddChild(this); } this.hierarchyChanged = true; this.updateQueue.Enqueue(new CoreEvent(ThingMsg.id)); } } /// /// Indication whether this is a root thing /// public bool isRoot { get => this == localRoot || this.parent == null; } /// /// The children of this thing /// [NonSerialized] protected List children = new(); /// /// Add a child Thing to this Thing /// /// The Thing which should become a child /// @remark When the Thing is already a child, it will not be added again public void AddChild(Thing child) { if (children.Find(thing => thing == child) != null) return; child._parent = this; children.Add(child); } /// /// Remove the given thing as a child of this thing /// /// The child to remove /// True when the child was present or false when it was not found public bool RemoveChild(Thing child) { return children.Remove(child); } /// /// Get a child by thing Id /// /// The thing ID to find /// Look recursively through all descendants /// Thing GetChild(byte thingId, bool recurse = false) { foreach (Thing child in this.children) { if (child == null) continue; if (child.id == thingId) return child; if (recurse) { Thing foundChild = child.GetChild(thingId, recurse); if (foundChild != null) return foundChild; } } return null; } /// /// Find a child by name /// /// The name of the child thing /// Look recursively through all descendants /// The found thing or null when nothing is found Thing FindChild(string name, bool recurse = true) { foreach (Thing child in this.children) { if (child == null) continue; if (child.name == name) return child; if (recurse) { Thing foundChild = child.FindChild(name, recurse); if (foundChild != null) return foundChild; } } return null; } /// /// Indicator that the hierarchy of the thing has changed /// public bool hierarchyChanged = true; #endregion Hierarchy #region Pose private Spherical _position = Spherical.zero; /// /// The position of the thing in local space, in meters. /// public Spherical position { get { return _position; } set { if (_position != value) { _position = value; positionUpdated = true; updateQueue.Enqueue(new CoreEvent(PoseMsg.Id)); } } } /// /// Boolean indicating that the thing has an updated position /// public bool positionUpdated = false; private SwingTwist _orientation = SwingTwist.zero; /// /// The orientation of the thing in local space /// public SwingTwist orientation { get { return _orientation; } set { if (_orientation != value) { _orientation = value; orientationUpdated = true; updateQueue.Enqueue(new CoreEvent(PoseMsg.Id)); //OnOrientationChanged?.Invoke(); } } } /// /// Boolean indicating the thing has an updated orientation /// public bool orientationUpdated = false; private Spherical _linearVelocity = Spherical.zero; /// /// The linear velocity of the thing in local space in meters per second /// public Spherical linearVelocity { get => _linearVelocity; set { if (_linearVelocity != value) { _linearVelocity = value; linearVelocityUpdated = true; updateQueue.Enqueue(new CoreEvent(PoseMsg.Id)); } } } /// /// Boolean indicating the thing has an updated linear velocity /// public bool linearVelocityUpdated = false; private Spherical _angularVelocity = Spherical.zero; /// /// The angular velocity of the thing in local space in degrees per second /// public Spherical angularVelocity { get => _angularVelocity; set { if (_angularVelocity != value) { _angularVelocity = value; angularVelocityUpdated = true; updateQueue.Enqueue(new CoreEvent(PoseMsg.Id)); } } } /// /// Boolean indicating the thing has an updated angular velocity /// public bool angularVelocityUpdated = false; #endregion Pose #region Update /// /// Update de state of the thing /// /// When true, this will Update the descendants recursively public virtual void Update(bool recurse = false) { this.positionUpdated = false; this.orientationUpdated = false; this.linearVelocityUpdated = false; this.angularVelocityUpdated = false; this.hierarchyChanged = false; // should recurse over children... if (recurse) { for (byte childIx = 0; childIx < this.children.Count; childIx++) { Thing child = this.children[childIx]; if (child == null) continue; child.Update(recurse); } } } /// /// An event happened to this event /// /// The messageId indicates which kind of event happened public class CoreEvent { public CoreEvent(int messageId) { this.messageId = messageId; } /// /// The type of event happened. /// /// This value is filled with the Ids of the communication messages. public int messageId; }; /// /// Queue containing events happened to this thing /// public ConcurrentQueue updateQueue = new(); /// /// Get the current time in milliseconds /// /// The current time in milliseconds public static ulong GetTimeMs() { #if UNITY_5_3_OR_NEWER return (ulong)(UnityEngine.Time.time * 1000); #else return TimeManager.GetCurrentTimeMilliseconds(); #endif } #endregion Update /// /// Function used to generate binary data for this thing /// /// A byte array with the binary data /// @sa Passer::RoboidControl::BinaryMsg public virtual byte[] GenerateBinary() { return Array.Empty(); } /// /// Function used to process binary data received for this thing /// /// The binary data to process public virtual void ProcessBinary(byte[] bytes) { } } }