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 private Thing(Participant owner) { this.type = Type.Root; this.positionUpdated = true; this.orientationUpdated = true; this.hierarchyChanged = true; this.owner = owner; this.parent = null; } public static Thing CreateRoot(Participant owner) { return new Thing(owner); } static Thing localRoot { get { Participant participant = Participant.localParticipant; return participant.root; } } /* /// /// Create a new thing without communication abilities /// /// The type of thing (can use \ref RoboidControl::Thing::Type "Thing.Type") /// Invoke a OnNewThing event when the thing has been created public Thing(byte thingType = Type.Undetermined, bool invokeEvent = true) : this(ParticipantUDP.Isolated(), thingType, 0, invokeEvent) { } /// /// Create a new thing for a participant /// /// The owning participant /// The type of thing (can use \ref RoboidControl::Thing::Type "Thing.Type") /// The ID of the thing, leave out or set to zero to generate an ID /// Invoke a OnNewThing event when the thing has been created public Thing(Participant owner, byte thingType = Type.Undetermined, byte thingId = 0, bool invokeEvent = true) { this.owner = owner; this.id = thingId; this.type = thingType; if (this.owner != null) this.owner.Add(this); if (invokeEvent) { Participant.UpdateEvent e = new() { messageId = ThingMsg.id, thing = this }; this.owner.updateQueue.Enqueue(e); } } */ /// /// Create a new child thing /// /// The parent thing /// The type of thing (can use \ref RoboidControl::Thing::Type "Thing.Type") /// The ID of the thing, leave out or set to zero to generate an ID /// Invoke a OnNewThing event when the thing has been created /// The owner will be the same as the owner of the parent thing /* public Thing(Thing parent, byte thingType = Type.Undetermined, byte thingId = 0, bool invokeEvent = true) : this(parent.owner, thingType, thingId, invokeEvent) { this.parent = parent; } */ 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); } // public static Thing CreateRemote(Participant owner, byte thingId) { // Thing remoteThing = new(owner.root) { // id = thingId // }; // return remoteThing; // } /// /// 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 public bool terminate = false; #region Properties /// /// The participant owning this thing /// public Participant owner = null; /// /// 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; 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 /// public string modelUrl { get => _modelUrl; set { if (_modelUrl != value) { _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)); } } /// /// 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 /// /// 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 } /// /// Update de state of the thing /// /// When true, this will Update the descendants recursively public void Update(bool recurse = false) { Update(GetTimeMs(), recurse); } // #endif /// /// Update this thing /// /// he current clock time in milliseconds; if this is zero, the current time is retrieved automatically /// When true, this will Update the descendants recursively public virtual void Update(ulong currentTimeMs, 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(currentTimeMs, recurse); } } } public class CoreEvent { public CoreEvent(int messageId) { this.messageId = messageId; } public int messageId; // see the communication messages }; public ConcurrentQueue updateQueue = new(); #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) { } } }