diff --git a/doc/source/structures/vessels/attitudecontroller.rst b/doc/source/structures/vessels/attitudecontroller.rst new file mode 100644 index 0000000000..e178eba203 --- /dev/null +++ b/doc/source/structures/vessels/attitudecontroller.rst @@ -0,0 +1,251 @@ +.. _attitudecontroller: + +Attitude Controller +=================== + +A ship usually has various parts that can rotate or translate the ship. +(These include areodynamic control surfaces, engines with gimbaled thrust, +RCS thrusters, reaction wheels, and drain valves.) +Information about these controllers can be read, and in some cases set, +using the ``AttitudeController`` structure defined on this page. + +Example:: + + local controllers is ship:AttitudeControllers. + print(controllers[0]:part + " allows pitch? " + controllers[0]:allowPitch). + +.. structure:: AttitudeController + + .. list-table:: Members + :header-rows: 1 + :widths: 1 1 2 + + * - Suffix + - Type (units) + - Description + + * - :attr:`PART` + - :struct:`Part ` + - The part this controller belongs to. + * - :attr:`MODULE` + - :struct:`PartModule ` + - The module this controller belongs to. Will return false if there is no matching module. + * - :attr:`ALLOWPITCH` + - :ref:`Boolean ` + - Gets or sets wheter this controller should respond to pitch input. + * - :attr:`ALLOWYAW` + - :ref:`Boolean ` + - Gets or sets wheter this controller should respond to yaw input. + * - :attr:`ALLOWROLL` + - :ref:`Boolean ` + - Gets or sets wheter this controller should respond to roll input. + * - :attr:`ALLOWX` + - :ref:`Boolean ` + - Gets or sets wheter this controller should respond to x translation input. + * - :attr:`ALLOWY` + - :ref:`Boolean ` + - Gets or sets wheter this controller should respond to y translation input. + * - :attr:`ALLOWZ` + - :ref:`Boolean ` + - Gets or sets wheter this controller should respond to z translation input. + * - :attr:`HASCUSTOMTHROTTLE` + - :ref:`Boolean ` + - Wheter this controller has a custom throttle input. + * - :attr:`CUSTOMTHROTTLE` + - :ref:`scalar ` (%) + - The value the custom throttle. + * - :attr:`ROTATIONAUTHORITYLIMITER` + - :ref:`scalar ` (%) + - The authority limit for rotation. + * - :attr:`TRANSLATIONAUTHORITYLIMITER` + - :ref:`scalar ` (%) + - The authority limit for translation. + * - :attr:`CONTROLLERTYPE` + - :ref:`string ` + - The type of the controller. + * - :attr:`STATUS` + - :ref:`string ` + - A string indicating more detailed status about the controller if available. + * - :attr:`RESPONSETIME` + - :ref:`scalar ` + - The reported responsetime of the controller. + * - :attr:`POSITIVEROTATION` + - :struct:`AttitudeCorrectionResult ` + - What is expected to happen when you provide a positive value to pitch, yaw, roll. + * - :attr:`NEGATIVEROTATION` + - :struct:`AttitudeCorrectionResult ` + - What is expected to happen when you provide a negative value to pitch, yaw, roll. + * - :meth:`RESPONSEFOR` + - :struct:`AttitudeCorrectionResult ` + - What is expected to happen for arbitrary combinations of pitch, yaw, roll, translate x, translate y, translate z, custom throttle. + + +.. note:: + + The rotation responses are simplified models of reality and are likely to be off to various degrees. + + + +.. _attitudecontroller_PART: + +.. attribute:: AttitudeController:PART + + :access: Get + :type: :struct:`Part ` + + The part this controller belongs to. + +.. _attitudecontroller_MODULE: + +.. attribute:: AttitudeController:MODULE + + :access: Get only + :type: :struct:`PartModule ` + + The module this controller belongs to. Will return false if there is no matching module. + +.. _attitudecontroller_ALLOWPITCH: + +.. attribute:: AttitudeController:ALLOWPITCH + + :access: Get/Set + :type: :ref:`boolean ` + + Determines whether this controller is allowed to respond to pitch input. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_ALLOWYAW: + +.. attribute:: AttitudeController:ALLOWYAW + + :access: Get/Set + :type: :ref:`boolean ` + + Determines whether this controller is allowed to respond to yaw input. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_ALLOWROLL: + +.. attribute:: AttitudeController:ALLOWROLL + + :access: Get/Set + :type: :ref:`boolean ` + + Determines whether this controller is allowed to respond to roll input. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_ALLOWX: + +.. attribute:: AttitudeController:ALLOWX + + :access: Get/Set + :type: :ref:`boolean ` + + Determines whether this controller is allowed to respond to translation fore input. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_ALLOWY: + +.. attribute:: AttitudeController:ALLOWY + + :access: Get/Set + :type: :ref:`boolean ` + + Determines whether this controller is allowed to respond to translation top input. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_ALLOWZ: + +.. attribute:: AttitudeController:ALLOWZ + + :access: Get/Set + :type: :ref:`boolean ` + + Determines whether this controller is allowed to respond to translation star input. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_HASCUSTOMTHROTTLE: + +.. attribute:: AttitudeController:HASCUSTOMTHROTTLE` + + :access: Get only + :type: :ref:`boolean ` + + Returns true if this controller has a custom throttle you can modify. + +.. _attitudecontroller_CUSTOMTHROTTLE: + +.. attribute:: AttitudeController:CUSTOMTHROTTLE + + :access: Get/Set + :type: :ref:`scalar ` (%) + + Sets the custom throttle for this controller. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_ROTATIONAUTHORITYLIMITER: + +.. attribute:: AttitudeController:ROTATIONAUTHORITYLIMITER + + :access: Get/Set + :type: :ref:`scalar ` (%) + + Sets the authority limiter used during rotation. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_TRANSLATIONAUTHORITYLIMITER: + +.. attribute:: AttitudeController:TRANSLATIONAUTHORITYLIMITER + + :access: Get/Set + :type: :ref:`scalar ` (%) + + Sets the authority limiter used during translation. Not all controller types allow setting this, make sure to check the value after setting it! + +.. _attitudecontroller_CONTROLLERTYPE: + +.. attribute:: AttitudeController:CONTROLLERTYPE + + :access: Get only + :type: :ref:`string ` + + The type of the attitude controller (ENGINE, DRAINVALVE, ROTOR, RCS, REACTIONWHEEL) or UNKNOWN if the exact type is unknown. + +.. _attitudecontroller_STATUS: + +.. attribute:: AttitudeController:STATUS + + :access: Get only + :type: :ref:`string ` + + The status of the controller if known. UNKNOWN otherwise. + +.. _attitudecontroller_RESPONSETIME: + +.. attribute:: AttitudeController:RESPONSETIME + + :access: Get only + :type: :ref:`scalar ` + + The reported response time of this controller. + +.. _attitudecontroller_POSITIVEROTATION: + +.. attribute:: AttitudeController:POSITIVEROTATION + + :access: Get only + :type: :struct:`AttitudeCorrectionResult ` + + What is expected to happen when you provide a positive value to pitch, yaw, roll. + +.. _attitudecontroller_NEGATIVEROTATION: + +.. attribute:: AttitudeController:NEGATIVEROTATION + + :access: Get only + :type: :struct:`AttitudeCorrectionResult ` + + What is expected to happen when you provide a negative value to pitch, yaw, roll. + +.. _attitudecontroller_RESPONSEFOR: + +.. method:: AttitudeController:RESPONSEFOR(pitchYawRollInput, translateXYZInput, throttle) + + :parameter pitchYawRollInput: A vector describing user pitch, yaw, roll input between -1 and 1. + :parameter translateXYZInput: A vector describing user fore, top, star translation input between -1 and 1. + :parameter throttle: A scalar representing the custom throttle value in percent. + :type: :struct:`AttitudeCorrectionResult ` + + Simulates the effect of the given input on the ship. This allows computing things like RCS thruster inbalances. diff --git a/doc/source/structures/vessels/attitudecorrectionresult.rst b/doc/source/structures/vessels/attitudecorrectionresult.rst new file mode 100644 index 0000000000..c32d9fd2da --- /dev/null +++ b/doc/source/structures/vessels/attitudecorrectionresult.rst @@ -0,0 +1,23 @@ +.. _attitudecorrectionresult: + +Attitude Correction Result +====== + +When you perform a control action on a ship (yaw, pitch, roll, fore, top, star, throttle) this always has two effects on the ship. Part of the impulse will be be imparted as rotation and part of it will be translation. + +.. structure:: AttitudeCorrectionResult + + .. list-table:: Members + :header-rows: 1 + :widths: 1 1 2 + + * - Suffix + - Type (units) + - Description + + * - :attr:`TORQUE` + - :struct:`Vector ` + - The torque vector (pitch, roll, yaw). + * - :attr:`TRANSLATION` + - :struct:`Vector ` + - The translation force (fore, top, star) diff --git a/doc/source/structures/vessels/part.rst b/doc/source/structures/vessels/part.rst index 867890266a..55820dae86 100644 --- a/doc/source/structures/vessels/part.rst +++ b/doc/source/structures/vessels/part.rst @@ -144,6 +144,9 @@ These are the generic properties every PART has. You can obtain a list of values * - :meth:`ALLTAGGEDPARTS` - :struct:`List` (of :struct:`Part`) - Search the branch from here down for all parts with a non-blank tag name. + * - :meth:`ATTITUDECONTROLLERS` + - :struct:`List` (of :struct:`AttitudeController`) + - List of Attitude Controllers in this part. .. attribute:: Part:NAME @@ -609,3 +612,8 @@ These are the generic properties every PART has. You can obtain a list of values branch of the vessel's part tree from the current part down through its children and its children's children and so on. +.. method:: Vessel::ATTITUDECONTROLLERS() + + :return: :struct:`List` of :struct:`AttitudeController` objects + + Return all Attitude Controllers in this part. \ No newline at end of file diff --git a/doc/source/structures/vessels/vessel.rst b/doc/source/structures/vessels/vessel.rst index 1ab6e8b93b..6b290e88de 100644 --- a/doc/source/structures/vessels/vessel.rst +++ b/doc/source/structures/vessels/vessel.rst @@ -57,6 +57,7 @@ Vessels are also :ref:`Orbitable`, and as such have all the associate :attr:`SIZECLASS` :struct:`String` Return the size class for an asteroid-like object :attr:`ANGULARMOMENTUM` :struct:`Vector` In :ref:`SHIP_RAW ` :attr:`ANGULARVEL` :struct:`Vector` In :ref:`SHIP_RAW ` + :attr:`MOMENTOFINERTIA` :struct:`Vector` Moment of inertia over pitch, yaw, roll :attr:`SENSORS` :struct:`VesselSensors` Sensor data :attr:`LOADED` :struct:`Boolean` loaded into KSP physics engine or "on rails" :attr:`UNPACKED` :struct:`Boolean` The ship has individual parts unpacked @@ -83,6 +84,7 @@ Vessels are also :ref:`Orbitable`, and as such have all the associate :meth:`PARTSINGROUP(group)` :struct:`List` :struct:`Parts ` by action group :meth:`MODULESINGROUP(group)` :struct:`List` :struct:`PartModules ` by action group :meth:`ALLTAGGEDPARTS()` :struct:`List` :struct:`Parts ` that have non-blank nametags + :meth:`ATTITUDECONTROLLERS()` :struct:`List` :struct:`Attitude Controllers ` present on this ship. :attr:`CREWCAPACITY` :struct:`scalar` Crew capacity of this vessel :meth:`CREW()` :struct:`List` all :struct:`CrewMembers ` :attr:`CONNECTION` :struct:`Connection` Returns your connection to this vessel @@ -420,6 +422,10 @@ Vessels are also :ref:`Orbitable`, and as such have all the associate congruent with how VESSEL:ANGULARMOMENTUM is expressed, and for backward compatibility with older kOS scripts. +.. attribute:: Vessel:MOMENTOFINERTIA + + The moment of inertia of this ship over the pitch, yaw and roll axis. + .. attribute:: Vessel:SENSORS :type: :struct:`VesselSensors` @@ -628,6 +634,12 @@ Vessels are also :ref:`Orbitable`, and as such have all the associate Return all parts who's nametag isn't blank. For more information, see :ref:`ship parts and modules `. +.. method:: Vessel::ATTITUDECONTROLLERS() + + :return: :struct:`List` of :struct:`AttitudeController` objects + + Return all Attitude Controllers on this ship. + .. attribute:: Vessel:CREWCAPACITY :type: :ref:`scalar ` diff --git a/src/kOS/Control/SteeringManager.cs b/src/kOS/Control/SteeringManager.cs index 10dfd63fe8..8ba17e407d 100644 --- a/src/kOS/Control/SteeringManager.cs +++ b/src/kOS/Control/SteeringManager.cs @@ -592,7 +592,7 @@ public void UpdateStateVectors() // TODO: If stock vessel.MOI stops being so weird, we might be able to change the following line // into this instead. (See the comment on FindMOI()'s header): // momentOfInertia = shared.Vessel.MOI; - momentOfInertia = FindMoI(); + momentOfInertia = SteeringManager.FindMoI(shared.Vessel); adjustTorque = Vector3d.zero; measuredTorque = Vector3d.Scale(momentOfInertia, angularAcceleration); @@ -681,7 +681,7 @@ public void UpdateTorque() /// See https://github.com/KSP-KOS/KOS/issues/2814 for why this wrapper around KSP's API call exists. /// /// - void CorrectedGetPotentialTorque(ITorqueProvider tp, out Vector3 pos, out Vector3 neg) + public void CorrectedGetPotentialTorque(ITorqueProvider tp, out Vector3 pos, out Vector3 neg) { if (tp is ModuleRCS) { @@ -776,19 +776,23 @@ void CorrectedGetPotentialTorque(ITorqueProvider tp, out Vector3 pos, out Vector /// would expect "control from here" to do.) /// /// TODO: Check this again after each KSP stock release to see if it's been changed or not. - public Vector3 FindMoI() + public static Vector3 FindMoI(Vessel vessel) { + var vesTransform = vessel.ReferenceTransform; + var vesRotation = vesTransform.rotation * Quaternion.Euler(-90, 0, 0); + var vesCenterOfMass = vessel.CoMD; + var tensor = Matrix4x4.zero; Matrix4x4 partTensor = Matrix4x4.identity; Matrix4x4 inertiaMatrix = Matrix4x4.identity; Matrix4x4 productMatrix = Matrix4x4.identity; - foreach (var part in Vessel.Parts) + foreach (var part in vessel.Parts) { if (part.rb != null) { KSPUtil.ToDiagonalMatrix2(part.rb.inertiaTensor, ref partTensor); - Quaternion rot = Quaternion.Inverse(vesselRotation) * part.transform.rotation * part.rb.inertiaTensorRotation; + Quaternion rot = Quaternion.Inverse(vesRotation) * part.transform.rotation * part.rb.inertiaTensorRotation; Quaternion inv = Quaternion.Inverse(rot); Matrix4x4 rotMatrix = Matrix4x4.TRS(Vector3.zero, rot, Vector3.one); @@ -797,7 +801,7 @@ public Vector3 FindMoI() // add the part inertiaTensor to the ship inertiaTensor KSPUtil.Add(ref tensor, rotMatrix * partTensor * invMatrix); - Vector3 position = vesselTransform.InverseTransformDirection(part.rb.position - centerOfMass); + Vector3 position = vesTransform.InverseTransformDirection(part.rb.position - vesCenterOfMass); // add the part mass to the ship inertiaTensor KSPUtil.ToDiagonalMatrix2(part.rb.mass * position.sqrMagnitude, ref inertiaMatrix); diff --git a/src/kOS/Function/BuildList.cs b/src/kOS/Function/BuildList.cs index 84376baff6..900275de7e 100644 --- a/src/kOS/Function/BuildList.cs +++ b/src/kOS/Function/BuildList.cs @@ -36,6 +36,7 @@ public override void Execute(SharedObjects shared) case "parts": case "engines": case "rcs": + case "reactionwheels": case "sensors": case "elements": case "dockingports": diff --git a/src/kOS/Function/PrintList.cs b/src/kOS/Function/PrintList.cs index 0667db142c..52ef5850e3 100644 --- a/src/kOS/Function/PrintList.cs +++ b/src/kOS/Function/PrintList.cs @@ -66,6 +66,10 @@ public override void Execute(SharedObjects shared) list = GetRCSList(shared); break; + case "reactionwheels": + list = GetReactionWheelList(shared); + break; + case "sensors": list = GetSensorList(shared); break; @@ -292,6 +296,27 @@ private kList GetRCSList(SharedObjects shared) return list; } + private kList GetReactionWheelList(SharedObjects shared) + { + var list = new kList(); + list.AddColumn("ID", 12, ColumnAlignment.Left); + list.AddColumn("Name", 28, ColumnAlignment.Left); + + foreach (Part part in shared.Vessel.Parts) + { + foreach (PartModule module in part.Modules) + { + var wheel = module as ModuleReactionWheel; + if (wheel != null) + { + list.AddItem(part.ConstructID(), part.partInfo.name); + } + } + } + + return list; + } + private kList GetSensorList(SharedObjects shared) { var list = new kList(); diff --git a/src/kOS/Suffixed/AttitudeController.cs b/src/kOS/Suffixed/AttitudeController.cs new file mode 100644 index 0000000000..8072cb756b --- /dev/null +++ b/src/kOS/Suffixed/AttitudeController.cs @@ -0,0 +1,657 @@ +using Expansions.Serenity; +using kOS.Control; +using kOS.Module; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Suffixed.Part; +using kOS.Suffixed.PartModuleField; +using kOS.Utilities; +using System.Linq; +using System.Collections.Generic; +using System; +using UnityEngine; + +namespace kOS.Suffixed +{ + [kOS.Safe.Utilities.KOSNomenclature("AttitudeController")] + public class AttitudeController : Structure + { + public AttitudeController(PartValue part, PartModuleFields module, ITorqueProvider provider) + { + TorqueProvider = provider; + this.Part = part; + + AddSuffix("PART", new Suffix(() => part, "The part this AttitudeController belongs to.")); + AddSuffix("MODULE", new Suffix(() => + { + if (module == null) + return new BooleanValue(false); + return module; + }, "The module this AttitudeController belongs to.")); + AddSuffix("ALLOWPITCH", new SetSuffix(() => AllowPitch, (v) => { AllowPitch = v; }, "Should this attitude controller respond to pitch input?")); + AddSuffix("ALLOWROLL", new SetSuffix(() => AllowRoll, (v) => { AllowRoll = v; }, "Should this attitude controller respond to roll input?")); + AddSuffix("ALLOWYAW", new SetSuffix(() => AllowYaw, (v) => { AllowYaw = v; }, "Should this attitude controller respond to yaw input?")); + AddSuffix("ALLOWX", new SetSuffix(() => AllowX, (v) => { AllowX = v; }, "Should this attitude controller respond to translation X input?")); + AddSuffix("ALLOWY", new SetSuffix(() => AllowY, (v) => { AllowY = v; }, "Should this attitude controller respond to translation Y input?")); + AddSuffix("ALLOWZ", new SetSuffix(() => AllowZ, (v) => { AllowZ = v; }, "Should this attitude controller respond to translation Z input?")); + + AddSuffix("HASCUSTOMTHROTTLE", new Suffix(() => HasCustomThrottle, "Does this attitude controller have an independent custom throttle?")); + AddSuffix("CUSTOMTHROTTLE", new SetSuffix(() => CustomThrottle, (v) => { CustomThrottle = v; }, "The value of the independent custom throttle.")); + AddSuffix("CONTROLLERTYPE", new Suffix(() => ControllerType, "The type of controller.")); + AddSuffix("STATUS", new Suffix(() => Status, "The status of the controller.")); + + AddSuffix("RESPONSETIME", new Suffix(() => ResponseTime, "The response speed of the controller.")); + + AddSuffix("POSITIVEROTATION", new Suffix(() => positiveRotation, "What is the torque applied when giving a positive input on pitch, roll and yaw.")); + AddSuffix("NEGATIVEROTATION", new Suffix(() => negativeRotation, "What is the torque applied when giving a negative input on pitch, roll and yaw.")); + + AddSuffix("ROTATIONAUTHORITYLIMITER", new SetSuffix(() => RotationAuthorityLimiter, (v) => { RotationAuthorityLimiter = v; }, "The authority limit for rotating.")); + AddSuffix("TRANSLATIONAUTHORITYLIMITER", new SetSuffix(() => TranslationAuthorityLimiter, (v) => { TranslationAuthorityLimiter = v; }, "The authority limit for translating.")); + + AddSuffix("RESPONSEFOR", new ThreeArgsSuffix((rotation, translation, thrust) => + { + return GetResponseFor((float)rotation.X, (float)rotation.Y, (float)rotation.Z, (float)translation.X, (float)translation.Y, (float)translation.Z, thrust); + }, "What is the torque applied when setting the following controls.")); + } + + public static AttitudeController FromModule(PartValue part, PartModuleFields moduleStructure, PartModule module) + { + if (module is ModuleReactionWheel) + return new AttitudeControllerReactionWheel(part, moduleStructure, (ModuleReactionWheel)module); + if (module is ModuleRCS) + return new AttitudeControllerRCS(part, moduleStructure, (ModuleRCS)module); + if (module is ModuleControlSurface) + return new AttitudeControllerControlSurface(part, moduleStructure, (ModuleControlSurface)module); + if (module is ModuleRoboticServoRotor) + return new AttitudeControllerRotor(part, moduleStructure, (ModuleRoboticServoRotor)module); + if (module is ModuleEngines) + return new AttitudeControllerEngine(part, moduleStructure); + if (module is ModuleResourceDrain) + return new AttitudeControllerDrainValve(part, moduleStructure, (ModuleResourceDrain)module); + if (module is ModuleGimbal) + return null; // Already covered by engine attitude controller + if (module is ITorqueProvider) + return new AttitudeController(part, moduleStructure, (ITorqueProvider)module); + return null; + } + + protected PartValue Part { get; private set; } + protected ITorqueProvider TorqueProvider { get; private set; } + public virtual bool AllowPitch { get { return false; } set { } } + public virtual bool AllowRoll { get { return false; } set { } } + public virtual bool AllowYaw { get { return false; } set { } } + public virtual bool AllowX { get { return false; } set { } } + public virtual bool AllowY { get { return false; } set { } } + public virtual bool AllowZ { get { return false; } set { } } + + public virtual float RotationAuthorityLimiter { get { return 0; } set { } } + public virtual float TranslationAuthorityLimiter { get { return 0; } set { } } + + public virtual bool HasCustomThrottle { get { return false; } } + public virtual float CustomThrottle { get { return 0; } set { } } + public virtual string ControllerType { get { return "UNKNOWN"; } } + public virtual string Status { get { return "UNKNOWN"; } } + public virtual float ResponseTime { get { return 0; } set { } } + + + public override string ToString() + { + return "AttitudeController(" + ControllerType + ")"; + } + + protected void GetPotentialTorque(out Vector3 positive, out Vector3 negative) + { + var controlParameter = kOSVesselModule.GetInstance(Part.Shared.Vessel).GetFlightControlParameter("steering"); + var steeringManager = controlParameter as SteeringManager; + + if (steeringManager == null) + { + positive = Vector3.zero; + negative = Vector3.zero; + return; + } + Vector3 pos = Vector3.zero; + Vector3 neg = Vector3.zero; + steeringManager.CorrectedGetPotentialTorque(TorqueProvider, out pos, out neg); + positive.x = pos.x; + positive.y = pos.z; + positive.z = pos.y; + negative.x = neg.x; + negative.y = neg.z; + negative.z = neg.y; + } + public virtual AttitudeCorrectionResult positiveRotation + { + get + { + Vector3 pos = Vector3.zero; + Vector3 _ = Vector3.zero; + GetPotentialTorque(out pos, out _); + + AttitudeCorrectionResult estimate = GetResponseFor(1, 1, 1, 0, 0, 0, CustomThrottle); + return new AttitudeCorrectionResult(new Vector(pos), estimate.translation); + } + } + public virtual AttitudeCorrectionResult negativeRotation + { + get + { + Vector3 _ = Vector3.zero; + Vector3 neg = Vector3.zero; + GetPotentialTorque(out _, out neg); + + AttitudeCorrectionResult estimate = GetResponseFor(-1, -1, -1, 0, 0, 0, CustomThrottle); + return new AttitudeCorrectionResult(new Vector(neg), estimate.translation); + } + } + public virtual AttitudeCorrectionResult GetResponseFor(float pitch, float yaw, float roll, float x, float y, float z, float throttle) + { + return new AttitudeCorrectionResult(Vector.Zero, Vector.Zero); + } + + protected Vector3 StarVector { get { return VesselUtils.GetFacing(Part.Shared.Vessel).Rotation * Vector3.right; } } + protected Vector3 TopVector { get { return VesselUtils.GetFacing(Part.Shared.Vessel).Rotation * Vector3.up; } } + protected Vector3 ForeVector { get { return VesselUtils.GetFacing(Part.Shared.Vessel).Rotation * Vector3.forward; } } + protected Vector3 PitchVector(Vector3 lever) + { + Vector3 projectedLever = Vector3.ProjectOnPlane(lever, StarVector); + if (projectedLever.sqrMagnitude < 0.0001) + return Vector3.zero; + return Vector3.Cross(projectedLever.normalized, StarVector); + } + protected Vector3 YawVector(Vector3 lever) + { + Vector3 projectedLever = Vector3.ProjectOnPlane(lever, TopVector); + if (projectedLever.sqrMagnitude < 0.0001) + return Vector3.zero; + return Vector3.Cross(projectedLever.normalized, TopVector); + } + protected Vector3 RollVector(Vector3 lever) + { + Vector3 projectedLever = Vector3.ProjectOnPlane(lever, ForeVector); + if (projectedLever.sqrMagnitude < 0.0001) + return Vector3.zero; + return Vector3.Cross(projectedLever.normalized, ForeVector); + } + protected Vector3 PositionToLever(Vector3 position) + { + Vector3 shipCenterOfMass = Part.Shared.Vessel.CoMD; + return position - shipCenterOfMass; + } + public static Direction GetTransformFacing(Transform transform) + { + var rotation = transform.rotation; + Quaternion facing = Quaternion.Inverse(Quaternion.Euler(90, 0, 0) * Quaternion.Inverse(rotation) * Quaternion.identity); + return new Direction(facing); + } + + protected AttitudeCorrectionResult SplitComponents(Vector3 lever, Vector3 thrust) + { + Vector3 pitchVector = PitchVector(lever); + Vector3 yawVector = YawVector(lever); + Vector3 rollVector = RollVector(lever); + + Vector3 pitchLever = Vector3.ProjectOnPlane(lever, StarVector); + Vector3 yawLever = Vector3.ProjectOnPlane(lever, TopVector); + Vector3 rollLever = Vector3.ProjectOnPlane(lever, ForeVector); + + float resultingPitch = Vector3.Dot(thrust, pitchVector) * pitchLever.magnitude; + float resultingYaw = Vector3.Dot(thrust, yawVector) * yawLever.magnitude; + float resultingRoll = Vector3.Dot(thrust, rollVector) * rollLever.magnitude; + + // Whatever force not used for angular momentum is thrust + Vector3 remainingThrust = thrust; + if (pitchVector.magnitude > 0) + remainingThrust = Vector3.ProjectOnPlane(remainingThrust, pitchVector); + if (yawVector.magnitude > 0) + remainingThrust = Vector3.ProjectOnPlane(remainingThrust, yawVector); + if (rollVector.magnitude > 0) + remainingThrust = Vector3.ProjectOnPlane(remainingThrust, rollVector); + + float resultingX = Vector3.Dot(remainingThrust, StarVector); + float resultingY = Vector3.Dot(remainingThrust, TopVector); + float resultingZ = Vector3.Dot(remainingThrust, ForeVector); + + return new AttitudeCorrectionResult(new Vector(resultingPitch, resultingYaw, resultingRoll), new Vector(resultingX, resultingY, resultingZ)); + } + } + + [kOS.Safe.Utilities.KOSNomenclature("AttitudeController", KOSToCSharp = false)] + class AttitudeControllerReactionWheel : AttitudeController + { + public AttitudeControllerReactionWheel(PartValue part, PartModuleFields module, ModuleReactionWheel wheel) + :base(part, module, wheel) + { + this.wheel = wheel; + } + + protected ModuleReactionWheel wheel { get; private set; } + + public override string ControllerType { get { return "REACTIONWHEEL"; } } + public override string Status { get { return wheel.State.ToString().ToUpper(); } } + public override float RotationAuthorityLimiter { get { return wheel.authorityLimiter; } set { wheel.authorityLimiter = Math.Max(Math.Min(value, 100), 0); } } + public override bool AllowPitch { get { return true; } set { } } + public override bool AllowRoll { get { return true; } set { } } + public override bool AllowYaw { get { return true; } set { } } + public override float ResponseTime { get { return wheel.torqueResponseSpeed; } set { } } + public override AttitudeCorrectionResult GetResponseFor(float pitch, float yaw, float roll, float x, float y, float z, float throttle) + { + Vector3 pos = Vector3.zero; + Vector3 neg = Vector3.zero; + GetPotentialTorque(out pos, out neg); + Vector3 rotation = Vector3.zero; + rotation += new Vector3( + Math.Max(0, pitch) * pos.x, + Math.Max(0, yaw) * pos.z, + Math.Max(0, roll) * pos.y + ); + rotation += new Vector3( + Math.Min(0, pitch) * -neg.x, + Math.Min(0, yaw) * -neg.z, + Math.Min(0, roll) * -neg.y + ); + return new AttitudeCorrectionResult(new Vector(rotation), new Vector(Vector.Zero)); + } + } + + [kOS.Safe.Utilities.KOSNomenclature("AttitudeController", KOSToCSharp = false)] + class AttitudeControllerControlSurface : AttitudeController + { + public AttitudeControllerControlSurface(PartValue part, PartModuleFields module, ModuleControlSurface surface) + :base(part, module, surface) + { + this.surface = surface; + } + + protected ModuleControlSurface surface { get; private set; } + + public override string ControllerType { get { return "CONTROLSURFACE"; } } + public override bool AllowPitch { get { return !surface.ignorePitch; } set { surface.ignorePitch = !value; } } + public override bool AllowRoll { get { return !surface.ignoreRoll; } set { surface.ignoreRoll = !value; } } + public override bool AllowYaw { get { return !surface.ignoreYaw; } set { surface.ignoreYaw = !value; } } + public override float RotationAuthorityLimiter { get { return surface.authorityLimiter; } set { surface.authorityLimiter = Math.Max(Math.Min(value, 100), 0); } } + public override AttitudeCorrectionResult GetResponseFor(float pitch, float yaw, float roll, float x, float y, float z, float throttle) + { + Vector3 pos = Vector3.zero; + Vector3 neg = Vector3.zero; + GetPotentialTorque(out pos, out neg); + Vector3 rotation = Vector3.zero; + rotation += new Vector3( + Math.Max(0, pitch) * pos.x, + Math.Max(0, yaw) * pos.z, + Math.Max(0, roll) * pos.y + ); + rotation += new Vector3( + Math.Min(0, pitch) * -neg.x, + Math.Min(0, yaw) * -neg.z, + Math.Min(0, roll) * -neg.y + ); + return new AttitudeCorrectionResult(new Vector(rotation), new Vector(Vector.Zero)); + } + } + + // This assumes the rotor is connected with the base connected to the controller, not the rotating end. + [kOS.Safe.Utilities.KOSNomenclature("AttitudeController", KOSToCSharp = false)] + class AttitudeControllerRotor : AttitudeController + { + public AttitudeControllerRotor(PartValue part, PartModuleFields module, ModuleRoboticServoRotor rotor) + : base(part, module, null) + { + this.rotor = rotor; + } + + protected ModuleRoboticServoRotor rotor { get; private set; } + + public override string ControllerType { get { return "ROTOR"; } } + public override AttitudeCorrectionResult positiveRotation { get { return new AttitudeCorrectionResult(Vector.Zero, Vector.Zero); } } + public override AttitudeCorrectionResult negativeRotation { get { return new AttitudeCorrectionResult(Vector.Zero, Vector.Zero); } } + public override bool HasCustomThrottle { get { return true; } } + public override float CustomThrottle { get { return rotor.maxTorque; } set { rotor.maxTorque = Math.Max(Math.Min(value, 100), 0); } } + public override string Status { get { return rotor.motorState.ToUpper(); } } + public override float ResponseTime { get { return rotor.rotorSpoolTime; } set { } } + + public override AttitudeCorrectionResult GetResponseFor(float pitch, float yaw, float roll, float x, float y, float z, float throttle) + { + if (!rotor.servoIsMotorized) + return new AttitudeCorrectionResult(Vector.Zero, Vector.Zero); + // Likely makes too many assumptions about the axis direction but the rotation axis does not seem to be public. + Quaternion rotator = rotor.servoTransformRotation; + Vector3d facing = Part.GetFacing().Vector; + Vector3 axis = new Vector3((float)facing.x, (float)facing.y, (float)facing.z); + float pitchAlignment = Vector3.Dot(axis, StarVector); + float yawAlignment = Vector3.Dot(axis, TopVector); + float rollAlignment = Vector3.Dot(axis, ForeVector); + Vector3 rotationEffect = new Vector3(pitchAlignment, yawAlignment, rollAlignment); + + if (rotor.rotateCounterClockwise) + rotationEffect *= -1.0f; + rotationEffect *= rotor.servoMotorSize; + rotationEffect *= throttle / 100; + return new AttitudeCorrectionResult(new Vector(rotationEffect), new Vector(0, 0, 0)); + } + } + + [kOS.Safe.Utilities.KOSNomenclature("AttitudeController", KOSToCSharp = false)] + class AttitudeControllerEngine : AttitudeController + { + public AttitudeControllerEngine(PartValue part, PartModuleFields module) + : base(part, module, null) + { + // This assumes that: + // - nozzles are controlled by at most one gimbal + // - even though multiple nozzles are controlled by one gimbal, they can rotate independently + // Gimbal assignment is not accurate but I can't find a good way to do it. It succesfully falls back to the first gimbal for now. + + var maxFound = new Dictionary(); + gimbals = part.Part.Modules.OfType().ToList(); + gimbalLinking = new Dictionary(); + foreach (var engine in part.Part.Modules.OfType()) + { + gimbalLinking.Add(engine, null); + maxFound[engine] = 0; + if (gimbals.Any()) // Assign first gimbal by default + gimbalLinking[engine] = gimbals.First(); + } + foreach (var gimbal in gimbals) + { + if (gimbal.engineMultsList == null) + continue; + foreach (var sublist in gimbal.engineMultsList) + { + if (sublist == null) + continue; + foreach (var kv in sublist) + { + var engine = kv.Key; + var strength = kv.Value; + if (strength < 0.01) + continue; + if (!gimbalLinking.ContainsKey(engine) || gimbalLinking[engine] == null) + { + gimbalLinking[engine] = gimbal; + maxFound[engine] = 0; + } + + if (maxFound[engine] < strength) + { + gimbalLinking[engine] = gimbal; + maxFound[engine] = strength; + } + } + } + } + } + protected List gimbals { get; private set; } + protected Dictionary gimbalLinking { get; private set; } + + public override bool AllowPitch + { + get + { + foreach (var gimbal in gimbals) + if (gimbal.enablePitch) + return true; + return false; + } + set + { + foreach (var gimbal in gimbals) + gimbal.enablePitch = value; + } + } + public override bool AllowRoll + { + get + { + foreach (var gimbal in gimbals) + if (gimbal.enableRoll) + return true; + return false; + } + set + { + foreach (var gimbal in gimbals) + gimbal.enableRoll = value; + } + } + public override bool AllowYaw + { + get + { + foreach (var gimbal in gimbals) + if (gimbal.enableYaw) + return true; + return false; + } + set + { + foreach (var gimbal in gimbals) + gimbal.enableYaw = value; + } + } + public override AttitudeCorrectionResult positiveRotation { get { return GetResponseFor(1, 1, 1, 0, 0, 0, CustomThrottle); } } + public override AttitudeCorrectionResult negativeRotation { get { return GetResponseFor(-1, -1, -1, 0, 0, 0, CustomThrottle); } } + public override string ControllerType { get { return "ENGINE"; } } + + public override bool HasCustomThrottle + { + get + { + var engines = gimbalLinking.Keys; + foreach (var engine in engines) + if (engine.independentThrottle) + return true; + return false; + } + } + public override float CustomThrottle + { + get + { + var engines = gimbalLinking.Keys; + if (engines.Count == 0) + return 0; + return engines.Select((e) => e.independentThrottlePercentage).Average(); + } + set + { + var engines = gimbalLinking.Keys; + foreach (var engine in engines) + engine.independentThrottlePercentage = Math.Max(Math.Min(value, 100), 0); + } + } + public override float ResponseTime + { + get + { + var engines = gimbalLinking.Keys; + if (engines.Count == 0) + return 0; + return engines.Select((e) => e.throttleResponseRate).Average(); + } + } + + public override AttitudeCorrectionResult GetResponseFor(float pitch, float yaw, float roll, float x, float y, float z, float throttle) + { + // Assumes that engines burn to Facing * gimbal + // Assumes that gimbals have equal freedom in both axis + + var engines = gimbalLinking.Keys; + if (engines.Count == 0) + return new AttitudeCorrectionResult(new Vector(0, 0, 0), new Vector(0, 0, 0)); + Vector3d facing3d = Part.GetFacing().Vector; + Vector3 facing = new Vector3((float)facing3d.x, (float)facing3d.y, (float)facing3d.z); + + + float shipThrottle = (float)kOSVesselModule.GetInstance(Part.Shared.Vessel).GetFlightControlParameter("throttle").GetValue(); + + var result = new AttitudeCorrectionResult(new Vector(0, 0, 0), new Vector(0, 0, 0)); + + foreach (var engine in engines) + { + if (engine.thrustTransforms.Count == 0) + continue; + + float engineThrottle = throttle / 100; + if (!engine.independentThrottle) + throttle = shipThrottle; + + float thrust = engine.GetThrust(null, true, engineThrottle); + // I assume this is supposed to be normalized + float multiplierSum = engine.thrustTransformMultipliers.Sum(); + if (multiplierSum < 0.01) + multiplierSum = 1; // Handle all zero multipliers, assume equal thrust + float thrustPerNozzle = thrust / engine.thrustTransforms.Count; + + + for (int i = 0; i < engine.thrustTransforms.Count; i++) + { + float scaledMultiplier = engine.thrustTransformMultipliers[i] / multiplierSum; + if (scaledMultiplier < 0.01) + scaledMultiplier = 1; // Handle all zero multipliers, assume equal thrust + float nozzleThrust = thrustPerNozzle * scaledMultiplier; + + Vector3 thrustPosition = engine.thrustTransforms[i].position; + + Vector3 thrustVector = facing; + Vector3 lever = PositionToLever(thrustPosition); + Vector3 pitchVector = PitchVector(lever); + Vector3 yawVector = YawVector(lever); + Vector3 rollVector = RollVector(lever); + + if (gimbalLinking[engine] != null) + { + var gimbal = gimbalLinking[engine]; + + float maskedPitch = gimbal.enablePitch ? pitch : 0; + float maskedYaw = gimbal.enableYaw ? yaw : 0; + float maskedRoll = gimbal.enableRoll ? roll : 0; + Vector3 desiredDirection = maskedPitch * pitchVector + maskedYaw * yawVector + maskedRoll * rollVector; + if (desiredDirection.magnitude > 0.01) + desiredDirection = desiredDirection.normalized; + + Vector3 desiredGimbalResponse = Vector3.ProjectOnPlane(desiredDirection, facing); + float rangeDegrees = gimbal.gimbalRange * gimbal.gimbalLimiter / 100; + float maxDeflection = (float)Math.Tan(rangeDegrees / 180 * Math.PI); + if (desiredGimbalResponse.magnitude > maxDeflection) + { + desiredGimbalResponse = desiredGimbalResponse.normalized * maxDeflection; + } + thrustVector = (desiredGimbalResponse + facing * (1 - desiredGimbalResponse.magnitude)).normalized; //readd main engine thrust + } + thrustVector *= nozzleThrust; + + result += SplitComponents(lever, thrustVector); + } + } + + return result; + } + } + + [kOS.Safe.Utilities.KOSNomenclature("AttitudeController", KOSToCSharp = false)] + class AttitudeControllerDrainValve : AttitudeController + { + public AttitudeControllerDrainValve(PartValue part, PartModuleFields module, ModuleResourceDrain drain) + : base(part, module, null) + { + this.drain = drain; + } + + protected ModuleResourceDrain drain { get; private set; } + + public override string ControllerType { get { return "DRAIN"; } } + public override AttitudeCorrectionResult positiveRotation { get { return new AttitudeCorrectionResult(Vector.Zero, Vector.Zero); } } + public override AttitudeCorrectionResult negativeRotation { get { return new AttitudeCorrectionResult(Vector.Zero, Vector.Zero); } } + public override AttitudeCorrectionResult GetResponseFor(float pitch, float yaw, float roll, float x, float y, float z, float throttle) + { + // Assumes thrust comes from the part position + // Assumes all resources are drained (not always true) + + float ISP = 5.0f; // All resources in ResourcesGeneric.cfg have a drain ISP of 5. + var parent = Part.Parent; + float weightDelta = parent.Part.GetWetMass() - parent.Part.GetDryMass(); + float fuelFlow = weightDelta / 5; // Max throttle (20%) drains tank in 5 seconds + + float throttleFraction = Math.Min(Math.Max(throttle, 0), 20) / 20; + float thrust = fuelFlow * ISP * 9.80665f * throttleFraction; + + Vector3d facing = Part.GetFacing().Vector; + Vector3 lever = PositionToLever(Part.Part.transform.position); + + return SplitComponents(lever, facing * thrust); + } + public override bool HasCustomThrottle { get { return true; } } + public override float CustomThrottle + { + get { return drain.drainRate; } + set { drain.drainRate = Math.Min(Math.Max(value, 0), 20); } + } + } + + [kOS.Safe.Utilities.KOSNomenclature("AttitudeController", KOSToCSharp = false)] + class AttitudeControllerRCS : AttitudeController + { + public AttitudeControllerRCS(PartValue part, PartModuleFields module, ModuleRCS rcs) + : base(part, module, rcs) + { + this.rcs = rcs; + } + + protected ModuleRCS rcs { get; private set; } + + public override string ControllerType { get { return "RCS"; } } + public override bool AllowPitch { get { return rcs.enablePitch; } set { rcs.enablePitch = value; } } + public override bool AllowRoll { get { return rcs.enableRoll; } set { rcs.enableRoll = value; } } + public override bool AllowYaw { get { return rcs.enableYaw; } set { rcs.enableYaw = value; } } + public override bool AllowX { get { return rcs.enableX; } set { rcs.enableX = value; } } + public override bool AllowY { get { return rcs.enableY; } set { rcs.enableY = value; } } + public override bool AllowZ { get { return rcs.enableZ; } set { rcs.enableZ = value; } } + public override AttitudeCorrectionResult GetResponseFor(float pitch, float yaw, float roll, float x, float y, float z, float throttle) + { + var result = new AttitudeCorrectionResult(new Vector(0, 0, 0), new Vector(0, 0, 0)); + if (Part.Part.ShieldedFromAirstream || !rcs.rcsEnabled || !rcs.isEnabled || + rcs.isJustForShow || rcs.flameout || !rcs.rcs_active) + return result; + + var maskedPitch = rcs.enablePitch ? pitch : 0; + var maskedYaw = rcs.enableYaw ? yaw : 0; + var maskedRoll = rcs.enableRoll ? roll : 0; + var maskedX = rcs.enableX ? x : 0; + var maskedY = rcs.enableY ? y : 0; + var maskedZ = rcs.enableZ ? z : 0; + + foreach (var transform in rcs.thrusterTransforms) + { + Vector3 rcsPosFromCoM = transform.position - Part.Part.vessel.CurrentCoM; + Vector3 rcsThrustDir = rcs.useZaxis ? -transform.forward : transform.up; + float powerFactor = rcs.thrusterPower * rcs.thrustPercentage * 0.01f; + // Normally you'd check for precision mode to nerf powerFactor here, + // but kOS doesn't obey that. + Vector3 thrust = powerFactor * rcsThrustDir; + Vector3 torque = Vector3d.Cross(rcsPosFromCoM, thrust); + Vector3 translation = rcsPosFromCoM.normalized * (float)Vector3d.Dot(rcsPosFromCoM.normalized, thrust); + Vector3 transformedTorque = Part.Part.vessel.ReferenceTransform.InverseTransformDirection(torque); + Vector3 transformedTranslation = Part.Part.vessel.ReferenceTransform.InverseTransformDirection(translation); + + Vector3 desiredTorque = new Vector3(maskedPitch, maskedRoll, maskedYaw); + Vector3 desiredTranslation = new Vector3(maskedX, maskedY, maskedZ); + + // Big assumption here about how ksp prioritizes conflicing inputs + float alignment = Vector3.Dot(transformedTorque.normalized, desiredTorque) + + Vector3.Dot(transformedTranslation.normalized, desiredTranslation); + + // This simulates a bang-bang rcs, which makes it work for the positiveRotation case and the V(0,0,0) case. + // KSP seems to use partial thrust though, unsure how to model this. + if (alignment > 0) + { + result += new AttitudeCorrectionResult( + new Vector(transformedTorque.x, transformedTorque.z, transformedTorque.y), + new Vector(transformedTranslation.x, transformedTranslation.z, transformedTranslation.y) + ); + } + } + return result; + } + } +} diff --git a/src/kOS/Suffixed/AttitudeCorrectionResult.cs b/src/kOS/Suffixed/AttitudeCorrectionResult.cs new file mode 100644 index 0000000000..6b85bf8cb4 --- /dev/null +++ b/src/kOS/Suffixed/AttitudeCorrectionResult.cs @@ -0,0 +1,33 @@ +using kOS.Safe; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; +using System; + +namespace kOS.Suffixed +{ + [kOS.Safe.Utilities.KOSNomenclature("AttitudeCorrectionResult")] + public class AttitudeCorrectionResult : Structure + { + public AttitudeCorrectionResult(Vector torque, Vector translation) + { + this.torque = torque; + this.translation = translation; + + AddSuffix("TORQUE", new Suffix(() => torque, "The torque for this correction.")); + AddSuffix("TRANSLATION", new Suffix(() => translation, "The translation force for this correction.")); + } + + public Vector torque { get; private set; } + public Vector translation { get; private set; } + + public override string ToString() + { + return String.Format("AttitudeCorrectionResult(torque: {0}, translation: {1})", torque.ToString(), translation.ToString()); + } + + public static AttitudeCorrectionResult operator +(AttitudeCorrectionResult a, AttitudeCorrectionResult b) + { + return new AttitudeCorrectionResult(a.torque + b.torque, a.translation + b.translation); + } + } +} diff --git a/src/kOS/Suffixed/Part/PartValue.cs b/src/kOS/Suffixed/Part/PartValue.cs index 18ac77a049..796f9eec44 100644 --- a/src/kOS/Suffixed/Part/PartValue.cs +++ b/src/kOS/Suffixed/Part/PartValue.cs @@ -85,6 +85,7 @@ private void PartInitializeSuffixes() AddSuffix("PARTSTAGGED", new OneArgsSuffix(GetPartsTagged)); AddSuffix("PARTSTAGGEDPATTERN", new OneArgsSuffix(GetPartsTaggedPattern)); AddSuffix("ALLTAGGEDPARTS", new NoArgsSuffix(GetAllTaggedParts)); + AddSuffix("ATTITUDECONTROLLERS", new NoArgsSuffix(() => new ListValue(GetAttitudeControllers()))); } public BoundsValue GetBoundsValue() @@ -435,6 +436,26 @@ public ListValue GetAllTaggedParts() .Any(tag => !String.Equals(tag.nameTag, "", StringComparison.CurrentCultureIgnoreCase))), Shared); } + public IEnumerable GetAttitudeControllers() + { + var result = new List(); + bool foundEngine = false; + foreach (PartModule module in Part.Modules) + { + if (module is ModuleEngines) + { + if (foundEngine) + continue; + foundEngine = true; + } + PartModuleFields moduleStructure = PartModuleFieldsFactory.Construct(module, Shared); + var controller = AttitudeController.FromModule(this, moduleStructure, module); + if (controller != null) + result.Add(controller); + } + return result; + } + /// /// Return all the PartModules matching the condition given, in the parts /// tree starting from this part downward (this branch of the vessel parts tree) diff --git a/src/kOS/Suffixed/VesselTarget.cs b/src/kOS/Suffixed/VesselTarget.cs index 3e0929a58c..8fc8b4a7fc 100644 --- a/src/kOS/Suffixed/VesselTarget.cs +++ b/src/kOS/Suffixed/VesselTarget.cs @@ -9,7 +9,9 @@ using kOS.Safe.Utilities; using kOS.Suffixed.Part; using kOS.Utilities; +using kOS.Control; using System; +using System.Linq; using System.Collections.Generic; using UnityEngine; @@ -259,10 +261,9 @@ private void InitializeSuffixes() AddSuffix("DOCKINGPORTS", new NoArgsSuffix>(() => DockingPorts)); AddSuffix(new string[] { "DECOUPLERS", "SEPARATORS" }, new NoArgsSuffix>(() => Decouplers)); AddSuffix("ELEMENTS", new NoArgsSuffix(() => Vessel.PartList("elements", Shared))); - + AddSuffix("ATTITUDECONTROLLERS", new NoArgsSuffix(GetAttitudeControllers)); AddSuffix("ENGINES", new NoArgsSuffix(() => Vessel.PartList("engines", Shared))); AddSuffix("RCS", new NoArgsSuffix(() => Vessel.PartList("rcs", Shared))); - AddSuffix("CONTROL", new Suffix(GetFlightControl)); AddSuffix("BEARING", new Suffix(() => VesselUtils.GetTargetBearing(CurrentVessel, Vessel))); AddSuffix("HEADING", new Suffix(() => VesselUtils.GetTargetHeading(CurrentVessel, Vessel))); @@ -310,6 +311,12 @@ private void InitializeSuffixes() AddSuffix("CREWCAPACITY", new NoArgsSuffix(GetCrewCapacity)); AddSuffix("CONNECTION", new NoArgsSuffix(() => new VesselConnection(Vessel, Shared))); AddSuffix("MESSAGES", new NoArgsSuffix(() => GetMessages())); + AddSuffix("MOMENTOFINERTIA", new Suffix(() => + { + Vector3 moi = SteeringManager.FindMoI(Vessel); + // pitch, yaw, roll + return new Vector(moi.x, moi.z, moi.y); + })); AddSuffix("STARTTRACKING", new NoArgsVoidSuffix(StartTracking)); AddSuffix("STOPTRACKING", new NoArgsVoidSuffix(StopTracking)); @@ -415,6 +422,13 @@ public ScalarValue GetMaxThrustAt(ScalarValue atmPressure) return VesselUtils.GetMaxThrust(Vessel, atmPressure); } + public ListValue GetAttitudeControllers() + { + IEnumerable controllers = new AttitudeController[]{ }; + controllers = Parts.Aggregate(controllers, (result, part) => result.Concat(part.GetAttitudeControllers())); + return new ListValue(controllers); + } + private void RetypeVessel(StringValue value) { Vessel.vesselType = value.ToString().ToEnum(); diff --git a/src/kOS/kOS.csproj b/src/kOS/kOS.csproj index 336b73c56e..b5d66cee5a 100644 --- a/src/kOS/kOS.csproj +++ b/src/kOS/kOS.csproj @@ -168,6 +168,8 @@ + + @@ -339,4 +341,4 @@ - \ No newline at end of file +