using System; using System.Collections.Generic; 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; // Other 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 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) InvokeNewThing(this); } /// /// 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; } // /// // /// Create a new thing for the given participant // /// // /// The participant owning the thing // /// The network ID of the thing // /// The ID of the thing // /// The type of thing // public Thing(Participant owner, byte networkId, byte thingId, byte thingType = 0) { // this.owner = owner; // this.id = thingId; // this.type = thingType; // this.networkId = networkId; // // Console.Write($"New thing added to {owner}"); // this.owner.Add(this); // InvokeNewThing(this); // } /// /// 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; OnNameChanged?.Invoke(); } } } /// /// Event which is triggered when the name changes /// public event ChangeHandler OnNameChanged = delegate { }; public bool nameChanged = false; /// /// An URL pointing to the location where a model of the thing can be found /// public string modelUrl = ""; #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; } } /// /// 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; //OnPositionChanged?.Invoke(); } } } /// /// Event triggered when the pose has changed /// public event ChangeHandler OnPoseChanged = delegate { }; /// /// 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; //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; OnLinearVelocityChanged?.Invoke(_linearVelocity); } } } /// /// Event triggered when the linear velocity has changed /// public event SphericalHandler OnLinearVelocityChanged = delegate { }; /// /// 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; OnAngularVelocityChanged?.Invoke(_angularVelocity); } } } /// /// Event triggered when the angular velocity has changed /// public event SphericalHandler OnAngularVelocityChanged = delegate { }; /// /// 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) { if (this.positionUpdated || this.orientationUpdated) OnPoseChanged?.Invoke(); 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 delegate void ChangeHandler(); public delegate void SphericalHandler(Spherical v); public delegate void ThingHandler(Thing t); /// /// Event triggered when a new thing has been created /// public static event ThingHandler OnNewThing = delegate { }; /// /// Trigger the creation for the given thing /// /// The created thing public static void InvokeNewThing(Thing thing) { OnNewThing?.Invoke(thing); } #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) { } } }