Whiplash141

Gravity Alignment v26

Apr 8th, 2019
321
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 24.56 KB | None | 0 0
  1. /*
  2. /// Whip's Gravity Alignment Systems v26 - revision: 9/22/18 ///    
  3.  
  4. Written by Whiplash141    
  5. */
  6.  
  7. /*    
  8. ==============================    
  9.     You can edit these vars  
  10. ==============================  
  11. */
  12.  
  13. const string gyroExcludeName = "Exclude";
  14. const string statusScreenName = "Alignment"; //(Optional) Name of status screen
  15. const string shipName = "\nPFS - Nightjar Orbital Bomber"; //(Optional) Name of your ship
  16.  
  17. bool shouldAlign = true; //If the script should attempt to stabalize by default
  18. bool referenceOnSameGridAsProgram = true; //if true, only searches for reference blocks on
  19.                                           //the same grid as the program block (should help with docking small vessels)
  20.  
  21. const double angleTolerance = 0; //How many degrees the code will allow before it overrides user control
  22.  
  23. //---PID Constants
  24. const double proportionalConstant = 2;
  25. const double derivativeConstant = .5;
  26.  
  27. /*  
  28. ====================================================  
  29.     Don't touch anything below this <3 - Whiplash  
  30. ====================================================  
  31. */
  32.  
  33. const double updatesPerSecond = 10;
  34. const double timeFlashMax = .5; //in seconds  
  35. const double timeLimit = 1 / updatesPerSecond;
  36. double angleRoll = 0;
  37. double anglePitch = 0;
  38. double timeElapsed = 0;
  39. bool canTolerate = true;
  40. string stableStatus = ">> Disabled <<";
  41. string gravityMagnitudeString;
  42. string overrideStatus;
  43.  
  44. List<IMyGyro> gyros = new List<IMyGyro>();
  45. List<IMyShipController> shipControllers = new List<IMyShipController>();
  46.  
  47. PID pitchPID;
  48. PID rollPID;
  49.  
  50. Program()
  51. {
  52.     Runtime.UpdateFrequency = UpdateFrequency.Once;
  53.     Echo("If you can read this\nclick the 'Run' button!");
  54.     pitchPID = new PID(proportionalConstant, 0, derivativeConstant, -10, 10, timeLimit);
  55.     rollPID = new PID(proportionalConstant, 0, derivativeConstant, -10, 10, timeLimit);
  56. }
  57.  
  58. void Main(string arg, UpdateType updateSource)
  59. {
  60.     //------------------------------------------
  61.     //This is a bandaid
  62.     if ((Runtime.UpdateFrequency & UpdateFrequency.Update1) == 0)
  63.         Runtime.UpdateFrequency = UpdateFrequency.Update1;
  64.     //------------------------------------------
  65.    
  66.     timeElapsed += Runtime.TimeSinceLastRun.TotalSeconds;
  67.  
  68.     switch (arg.ToLower())
  69.     {
  70.         case "toggle":
  71.             if (!shouldAlign)
  72.             {
  73.                 shouldAlign = true;
  74.                 stableStatus = "<< Active >>";
  75.             }
  76.             else
  77.             {
  78.                 shouldAlign = false;
  79.                 stableStatus = ">> Disabled <<";
  80.             }
  81.             break;
  82.  
  83.         case "on":
  84.             shouldAlign = true;
  85.             stableStatus = "<< Active >>";
  86.             break;
  87.  
  88.         case "off":
  89.             shouldAlign = false;
  90.             stableStatus = ">> Disabled <<";
  91.             break;
  92.  
  93.         default:
  94.             break;
  95.     }
  96.  
  97.     if (timeElapsed >= timeLimit)
  98.     {
  99.         AlignWithGravity();
  100.         StatusScreens();
  101.         timeElapsed = 0;
  102.         Echo("Stabilizers on?: " + shouldAlign.ToString());
  103.     }
  104. }
  105.  
  106. bool ShouldFetch(IMyTerminalBlock block)
  107. {
  108.     if (block is IMyShipController)
  109.     {
  110.         if (referenceOnSameGridAsProgram)
  111.         {
  112.             return block.CubeGrid == Me.CubeGrid;
  113.         }
  114.         else
  115.         {
  116.             return true;
  117.         }
  118.     }
  119.     else
  120.     {
  121.         return false;
  122.     }
  123. }
  124.  
  125. IMyShipController GetControlledShipController(List<IMyShipController> controllers)
  126. {
  127.     if (controllers.Count == 0)
  128.         return null;
  129.    
  130.     foreach (IMyShipController thisController in controllers)
  131.     {
  132.         if (thisController.IsUnderControl && thisController.CanControlShip)
  133.             return thisController;
  134.     }
  135.  
  136.     return controllers[0];
  137. }
  138.  
  139. void AlignWithGravity()
  140. {
  141.     //---Find our refrence and comparision blocks    
  142.     GridTerminalSystem.GetBlocksOfType(shipControllers, ShouldFetch);
  143.  
  144.     //---Check for any cases that would lead to code failure
  145.     if (shipControllers.Count == 0)
  146.     {
  147.         Echo($"ERROR: No ship controller was found");
  148.         return;
  149.     }
  150.  
  151.     //---Assign our reference block
  152.     IMyShipController referenceBlock = GetControlledShipController(shipControllers);
  153.  
  154.     //---Populate gyro list
  155.     gyros.Clear();
  156.     GridTerminalSystem.GetBlocksOfType(gyros, block => block.CubeGrid == referenceBlock.CubeGrid && !block.CustomName.Contains(gyroExcludeName));
  157.  
  158.     if (gyros.Count == 0)
  159.     {
  160.         Echo("ERROR: No gyros found on ship");
  161.         return;
  162.     }
  163.  
  164.     //---Get gravity vector    
  165.     var referenceOrigin = referenceBlock.GetPosition();
  166.     var gravityVec = referenceBlock.GetNaturalGravity();
  167.     var gravityVecLength = gravityVec.Length();
  168.     gravityMagnitudeString = Math.Round(gravityVecLength, 2).ToString() + " m/s²";
  169.     if (gravityVec.LengthSquared() == 0)
  170.     {
  171.         gravityMagnitudeString = "No Gravity";
  172.  
  173.         foreach (IMyGyro thisGyro in gyros)
  174.         {
  175.             thisGyro.SetValue("Override", false);
  176.         }
  177.         overrideStatus = "";
  178.         stableStatus = ">> Disabled <<";
  179.  
  180.         shouldAlign = false;
  181.  
  182.         angleRoll = 0; angleRoll = 0;
  183.         return;
  184.     }
  185.    
  186.     Vector3D leftVec = Vector3D.Cross(-gravityVec, referenceBlock.WorldMatrix.Forward);
  187.     Vector3D forwardVec = Vector3D.Cross(gravityVec, leftVec);
  188.    
  189.     double temp; //not used for anything
  190.     GetRotationAngles(forwardVec, -gravityVec, referenceBlock.WorldMatrix, out temp, out anglePitch, out angleRoll);
  191.  
  192.     Echo("pitch angle:" + Math.Round((anglePitch / Math.PI * 180), 2).ToString() + " deg");
  193.     Echo("roll angle:" + Math.Round((angleRoll / Math.PI * 180), 2).ToString() + " deg");
  194.  
  195.  
  196.     //---Get Raw Deviation angle    
  197.     double rawDevAngle = Math.Acos(MathHelper.Clamp(gravityVec.Dot(referenceBlock.WorldMatrix.Forward) / gravityVec.Length() * 180 / Math.PI, -1, 1));
  198.  
  199.     //---Angle controller    
  200.     double rollSpeed = rollPID.Control(angleRoll); //Math.Round(angleRoll * proportionalConstant + (angleRoll - lastAngleRoll) / timeElapsed * derivativeConstant, 2);
  201.     double pitchSpeed = pitchPID.Control(anglePitch); //Math.Round(anglePitch * proportionalConstant + (anglePitch - lastAnglePitch) / timeElapsed * derivativeConstant, 2);                                                                                                                                                            //w.H]i\p
  202.  
  203.     var mouseInput = referenceBlock.RotationIndicator;
  204.    
  205.     //rollSpeed = rollSpeed / gyros.Count;
  206.     //pitchSpeed = pitchSpeed / gyros.Count;
  207.  
  208.     //---Check if we are inside our tolerances  
  209.     canTolerate = true;
  210.  
  211.     if (Math.Abs(anglePitch * 180 / Math.PI) > angleTolerance)
  212.     {
  213.         canTolerate = false;
  214.     }
  215.  
  216.     if (Math.Abs(angleRoll * 180 / Math.PI) > angleTolerance)
  217.     {
  218.         canTolerate = false;
  219.     }
  220.  
  221.     //---Set appropriate gyro override  
  222.     if (shouldAlign && !canTolerate)
  223.     {
  224.         //do gyros
  225.         ApplyGyroOverride(mouseInput.Y, pitchSpeed, rollSpeed, gyros, referenceBlock.WorldMatrix);
  226.  
  227.         overrideStatus = $"\n\n           SAFETY OVERRIDE ACTIVE"; //\nYaw : {yawSpeed}";
  228.        
  229.         /*timeFlash += timeElapsed;
  230.         if (timeFlash > timeFlashMax)
  231.         {
  232.             if (flashOn)
  233.             {
  234.                 overrideStatus = "\n\n           SAFETY OVERRIDE ACTIVE";
  235.                 flashOn = false;
  236.             }
  237.             else
  238.             {
  239.                 overrideStatus = "";
  240.                 flashOn = true;
  241.             }
  242.             timeFlash = 0;
  243.         }*/
  244.     }
  245.     else
  246.     {
  247.         foreach (IMyGyro thisGyro in gyros)
  248.         {
  249.             thisGyro.SetValue("Override", false);
  250.         }
  251.         overrideStatus = "";
  252.     }
  253. }
  254.  
  255. /*
  256. /// Whip's Get Rotation Angles Method v16 - 9/25/18 ///
  257. Dependencies: VectorMath
  258. Note: Set desiredUpVector to Vector3D.Zero if you don't care about roll
  259. */
  260. void GetRotationAngles(Vector3D desiredForwardVector, Vector3D desiredUpVector, MatrixD worldMatrix, out double yaw, out double pitch, out double roll)
  261. {
  262.     var localTargetVector = Vector3D.Rotate(desiredForwardVector, MatrixD.Transpose(worldMatrix));
  263.     var flattenedTargetVector = new Vector3D(localTargetVector.X, 0, localTargetVector.Z);
  264.  
  265.     yaw = VectorMath.AngleBetween(Vector3D.Forward, flattenedTargetVector) * Math.Sign(localTargetVector.X); //right is positive
  266.     if (Math.Abs(yaw) < 1E-6 && localTargetVector.Z > 0) //check for straight back case
  267.         yaw = Math.PI;
  268.  
  269.     if (Vector3D.IsZero(flattenedTargetVector)) //check for straight up case
  270.         pitch = MathHelper.PiOver2 * Math.Sign(localTargetVector.Y);
  271.     else
  272.         pitch = VectorMath.AngleBetween(localTargetVector, flattenedTargetVector) * Math.Sign(localTargetVector.Y); //up is positive
  273.  
  274.     if (Vector3D.IsZero(desiredUpVector))
  275.     {
  276.         roll = 0;
  277.         return;
  278.     }
  279.     var localUpVector = Vector3D.Rotate(desiredUpVector, MatrixD.Transpose(worldMatrix));
  280.     var flattenedUpVector = new Vector3D(localUpVector.X, localUpVector.Y, 0);
  281.     roll = VectorMath.AngleBetween(flattenedUpVector, Vector3D.Up) * Math.Sign(Vector3D.Dot(Vector3D.Right, flattenedUpVector));
  282. }
  283.  
  284. void StatusScreens()
  285. {
  286.     //---get the parts of our string  
  287.     double roll_deg = angleRoll / Math.PI * 180;
  288.     double pitch_deg = -anglePitch / Math.PI * 180;
  289.     string rollStatusString = AngleStatus(roll_deg);
  290.     string pitchStatusString = AngleStatus(pitch_deg);
  291.  
  292.     //---Construct our final string  
  293.     string statusScreenMessage = shipName
  294.         + "\nNatural Gravity: " + gravityMagnitudeString
  295.         + "\nStabilizer: " + stableStatus
  296.         + "\n\nRoll Angle: " + Math.Round(roll_deg, 2).ToString() + " degrees\n" + rollStatusString
  297.         + "\n\nPitch Angle: " + Math.Round(pitch_deg, 2).ToString() + " degrees\n" + pitchStatusString
  298.         + overrideStatus;
  299.  
  300.  
  301.     //---Write to screens  
  302.     var screens = new List<IMyTerminalBlock>();
  303.     GridTerminalSystem.SearchBlocksOfName(statusScreenName, screens, block => block is IMyTextPanel);
  304.  
  305.     if (screens.Count == 0)
  306.         return;
  307.    
  308.     foreach (IMyTextPanel thisScreen in screens)
  309.     {
  310.         var fontSize = thisScreen.FontSize;
  311.         //thisScreen.Font = "Monospace";
  312.         var wrappedText = TextHelper.CenterTextMonospace(statusScreenMessage, fontSize);
  313.        
  314.         thisScreen.WritePublicText(wrappedText);
  315.         if (!thisScreen.ShowText)
  316.             thisScreen.ShowPublicTextOnScreen();
  317.     }
  318. }
  319.  
  320. const string align_15 = "[-15](-)-------0----------[+15]";
  321. const string align_14 = "[-15]-(-)------0----------[+15]";
  322. const string align_12 = "[-15]--(-)-----0----------[+15]";
  323. const string align_10 = "[-15]---(-)----0----------[+15]";
  324. const string align_8 =  "[-15]----(-)---0----------[+15]";
  325. const string align_6 =  "[-15]-----(-)--0----------[+15]";
  326. const string align_4 =  "[-15]------(-)-0----------[+15]";
  327. const string align_2 =  "[-15]-------(-)0----------[+15]";
  328. const string align0 =   "[-15]---------(0)---------[+15]";
  329. const string align2 =   "[-15]----------0(-)-------[+15]";
  330. const string align4 =   "[-15]----------0-(-)------[+15]";
  331. const string align6 =   "[-15]----------0--(-)-----[+15]";
  332. const string align8 =   "[-15]----------0---(-)----[+15]";
  333. const string align10 =  "[-15]----------0----(-)---[+15]";
  334. const string align12 =  "[-15]----------0-----(-)--[+15]";
  335. const string align14 =  "[-15]----------0------(-)-[+15]";
  336. const string align15 =  "[-15]----------0-------(-)[+15]";
  337.  
  338. string AngleStatus(double angle)
  339. {
  340.     if (angle > 15)
  341.         return align15;
  342.     else if (angle > 14)
  343.         return align14;
  344.     else if (angle > 12)
  345.         return align12;
  346.     else if (angle > 10)
  347.         return align10;
  348.     else if (angle > 8)
  349.         return align8;
  350.     else if (angle > 6)
  351.         return align6;
  352.     else if (angle > 4)
  353.         return align4;
  354.     else if (angle > 2)
  355.         return align2;
  356.     else if (angle > -2)
  357.         return align0;
  358.     else if (angle > -4)
  359.         return align_2;
  360.     else if (angle > -6)
  361.         return align_4;
  362.     else if (angle > -8)
  363.         return align_6;
  364.     else if (angle > -10)
  365.         return align_8;
  366.     else if (angle > -12)
  367.         return align_10;
  368.     else if (angle > -14)
  369.         return align_12;
  370.     else if (angle > -15)
  371.         return align_14;
  372.     else
  373.         return align_15;
  374. }
  375.  
  376. Vector3D VectorProjection(Vector3D a, Vector3D b) //proj a on b    
  377. {
  378.     Vector3D projection = a.Dot(b) / b.LengthSquared() * b;
  379.     return projection;
  380. }
  381.  
  382. int VectorCompareDirection(Vector3D a, Vector3D b) //returns -1 if vectors return negative dot product
  383. {
  384.     double check = a.Dot(b);
  385.     if (check < 0)
  386.         return -1;
  387.     else
  388.         return 1;
  389. }
  390.  
  391. double VectorAngleBetween(Vector3D a, Vector3D b) //returns radians
  392. {
  393.     if (a.LengthSquared() == 0 || b.LengthSquared() == 0)
  394.         return 0;
  395.     else
  396.         return Math.Acos(MathHelper.Clamp(a.Dot(b) / a.Length() / b.Length(), -1, 1));
  397. }
  398.  
  399. //Whip's ApplyGyroOverride Method v11 - 3/22/19
  400. void ApplyGyroOverride(double yawSpeed, double pitchSpeed, double rollSpeed, List<IMyGyro> gyroList, MatrixD worldMatrix)
  401. {
  402.     var rotationVec = new Vector3D(-pitchSpeed, yawSpeed, rollSpeed); //because keen does some weird stuff with signs
  403.     var relativeRotationVec = Vector3D.TransformNormal(rotationVec, worldMatrix);
  404.  
  405.     foreach (var thisGyro in gyroList)
  406.     {
  407.         var transformedRotationVec = Vector3D.TransformNormal(relativeRotationVec, Matrix.Transpose(thisGyro.WorldMatrix));
  408.  
  409.         thisGyro.Pitch = (float)transformedRotationVec.X;
  410.         thisGyro.Yaw = (float)transformedRotationVec.Y;
  411.         thisGyro.Roll = (float)transformedRotationVec.Z;
  412.         thisGyro.GyroOverride = true;
  413.     }
  414. }
  415.  
  416. public static class VectorMath
  417. {
  418.     /// <summary>
  419.     ///  Normalizes a vector only if it is non-zero and non-unit
  420.     /// </summary>
  421.     public static Vector3D SafeNormalize(Vector3D a)
  422.     {
  423.         if (Vector3D.IsZero(a))
  424.             return Vector3D.Zero;
  425.  
  426.         if (Vector3D.IsUnit(ref a))
  427.             return a;
  428.  
  429.         return Vector3D.Normalize(a);
  430.     }
  431.  
  432.     /// <summary>
  433.     /// Reflects vector a over vector b with an optional rejection factor
  434.     /// </summary>
  435.     public static Vector3D Reflection(Vector3D a, Vector3D b, double rejectionFactor = 1) //reflect a over b
  436.     {
  437.         Vector3D project_a = Projection(a, b);
  438.         Vector3D reject_a = a - project_a;
  439.         return project_a - reject_a * rejectionFactor;
  440.     }
  441.  
  442.     /// <summary>
  443.     /// Rejects vector a on vector b
  444.     /// </summary>
  445.     public static Vector3D Rejection(Vector3D a, Vector3D b) //reject a on b
  446.     {
  447.         if (Vector3D.IsZero(a) || Vector3D.IsZero(b))
  448.             return Vector3D.Zero;
  449.  
  450.         return a - a.Dot(b) / b.LengthSquared() * b;
  451.     }
  452.  
  453.     /// <summary>
  454.     /// Projects vector a onto vector b
  455.     /// </summary>
  456.     public static Vector3D Projection(Vector3D a, Vector3D b)
  457.     {
  458.         if (Vector3D.IsZero(a) || Vector3D.IsZero(b))
  459.             return Vector3D.Zero;
  460.  
  461.         return a.Dot(b) / b.LengthSquared() * b;
  462.     }
  463.  
  464.     /// <summary>
  465.     /// Scalar projection of a onto b
  466.     /// </summary>
  467.     public static double ScalarProjection(Vector3D a, Vector3D b)
  468.     {
  469.         if (Vector3D.IsZero(a) || Vector3D.IsZero(b))
  470.             return 0;
  471.  
  472.         if (Vector3D.IsUnit(ref b))
  473.             return a.Dot(b);
  474.  
  475.         return a.Dot(b) / b.Length();
  476.     }
  477.  
  478.     /// <summary>
  479.     /// Computes angle between 2 vectors
  480.     /// </summary>
  481.     public static double AngleBetween(Vector3D a, Vector3D b) //returns radians
  482.     {
  483.         if (Vector3D.IsZero(a) || Vector3D.IsZero(b))
  484.             return 0;
  485.         else
  486.             return Math.Acos(MathHelper.Clamp(a.Dot(b) / Math.Sqrt(a.LengthSquared() * b.LengthSquared()), -1, 1));
  487.     }
  488.  
  489.     /// <summary>
  490.     /// Computes cosine of the angle between 2 vectors
  491.     /// </summary>
  492.     public static double CosBetween(Vector3D a, Vector3D b, bool useSmallestAngle = false) //returns radians
  493.     {
  494.         if (Vector3D.IsZero(a) || Vector3D.IsZero(b))
  495.             return 0;
  496.         else
  497.             return MathHelper.Clamp(a.Dot(b) / Math.Sqrt(a.LengthSquared() * b.LengthSquared()), -1, 1);
  498.     }
  499.  
  500.     /// <summary>
  501.     /// Returns if the normalized dot product between two vectors is greater than the tolerance.
  502.     /// This is helpful for determining if two vectors are "more parallel" than the tolerance.
  503.     /// </summary>
  504.     /// <param name="a"></param>
  505.     /// <param name="b"></param>
  506.     /// <param name="tolerance"></param>
  507.     /// <returns></returns>
  508.     public static bool IsDotProductWithinTolerance(Vector3D a, Vector3D b, double tolerance)
  509.     {
  510.         double dot = Vector3D.Dot(a, b);
  511.         double num = a.LengthSquared() * b.LengthSquared() * tolerance * tolerance;
  512.         return dot * dot > num;
  513.     }
  514. }
  515.  
  516. //Whip's PID controller class v6 - 11/22/17
  517. public class PID
  518. {
  519.     double _kP = 0;
  520.     double _kI = 0;
  521.     double _kD = 0;
  522.     double _integralDecayRatio = 0;
  523.     double _lowerBound = 0;
  524.     double _upperBound = 0;
  525.     double _timeStep = 0;
  526.     double _inverseTimeStep = 0;
  527.     double _errorSum = 0;
  528.     double _lastError = 0;
  529.     bool _firstRun = true;
  530.     bool _integralDecay = false;
  531.     public double Value { get; private set; }
  532.  
  533.     public PID(double kP, double kI, double kD, double lowerBound, double upperBound, double timeStep)
  534.     {
  535.         _kP = kP;
  536.         _kI = kI;
  537.         _kD = kD;
  538.         _lowerBound = lowerBound;
  539.         _upperBound = upperBound;
  540.         _timeStep = timeStep;
  541.         _inverseTimeStep = 1 / _timeStep;
  542.         _integralDecay = false;
  543.     }
  544.  
  545.     public PID(double kP, double kI, double kD, double integralDecayRatio, double timeStep)
  546.     {
  547.         _kP = kP;
  548.         _kI = kI;
  549.         _kD = kD;
  550.         _timeStep = timeStep;
  551.         _inverseTimeStep = 1 / _timeStep;
  552.         _integralDecayRatio = integralDecayRatio;
  553.         _integralDecay = true;
  554.     }
  555.  
  556.     public double Control(double error)
  557.     {
  558.         //Compute derivative term
  559.         var errorDerivative = (error - _lastError) * _inverseTimeStep;
  560.  
  561.         if (_firstRun)
  562.         {
  563.             errorDerivative = 0;
  564.             _firstRun = false;
  565.         }
  566.  
  567.         //Compute integral term
  568.         if (!_integralDecay)
  569.         {
  570.             _errorSum += error * _timeStep;
  571.  
  572.             //Clamp integral term
  573.             if (_errorSum > _upperBound)
  574.                 _errorSum = _upperBound;
  575.             else if (_errorSum < _lowerBound)
  576.                 _errorSum = _lowerBound;
  577.         }
  578.         else
  579.         {
  580.             _errorSum = _errorSum * (1.0 - _integralDecayRatio) + error * _timeStep;
  581.         }
  582.  
  583.         //Store this error as last error
  584.         _lastError = error;
  585.  
  586.         //Construct output
  587.         this.Value = _kP * error + _kI * _errorSum + _kD * errorDerivative;
  588.         return this.Value;
  589.     }
  590.    
  591.     public double Control(double error, double timeStep)
  592.     {
  593.         _timeStep = timeStep;
  594.         _inverseTimeStep = 1 / _timeStep;
  595.         return Control(error);
  596.     }
  597.  
  598.     public void Reset()
  599.     {
  600.         _errorSum = 0;
  601.         _lastError = 0;
  602.         _firstRun = true;
  603.     }
  604. }
  605.  
  606. // Whip's TextHelper Class v2
  607. public class TextHelper
  608. {
  609.     static StringBuilder textSB = new StringBuilder();
  610.     const float adjustedPixelWidth = (512f / 0.778378367f);
  611.     const int monospaceCharWidth = 24 + 1; //accounting for spacer
  612.     const int spaceWidth = 8;
  613.  
  614.     #region Default font
  615.         #region bigass dictionary
  616.     static Dictionary<char, int> _charWidths = new Dictionary<char, int>()
  617.     {
  618. {'.', 9},
  619. {'!', 8},
  620. {'?', 18},
  621. {',', 9},
  622. {':', 9},
  623. {';', 9},
  624. {'"', 10},
  625. {'\'', 6},
  626. {'+', 18},
  627. {'-', 10},
  628.  
  629. {'(', 9},
  630. {')', 9},
  631. {'[', 9},
  632. {']', 9},
  633. {'{', 9},
  634. {'}', 9},
  635.  
  636. {'\\', 12},
  637. {'/', 14},
  638. {'_', 15},
  639. {'|', 6},
  640.  
  641. {'~', 18},
  642. {'<', 18},
  643. {'>', 18},
  644. {'=', 18},
  645.  
  646. {'0', 19},
  647. {'1', 9},
  648. {'2', 19},
  649. {'3', 17},
  650. {'4', 19},
  651. {'5', 19},
  652. {'6', 19},
  653. {'7', 16},
  654. {'8', 19},
  655. {'9', 19},
  656.  
  657. {'A', 21},
  658. {'B', 21},
  659. {'C', 19},
  660. {'D', 21},
  661. {'E', 18},
  662. {'F', 17},
  663. {'G', 20},
  664. {'H', 20},
  665. {'I', 8},
  666. {'J', 16},
  667. {'K', 17},
  668. {'L', 15},
  669. {'M', 26},
  670. {'N', 21},
  671. {'O', 21},
  672. {'P', 20},
  673. {'Q', 21},
  674. {'R', 21},
  675. {'S', 21},
  676. {'T', 17},
  677. {'U', 20},
  678. {'V', 20},
  679. {'W', 31},
  680. {'X', 19},
  681. {'Y', 20},
  682. {'Z', 19},
  683.  
  684. {'a', 17},
  685. {'b', 17},
  686. {'c', 16},
  687. {'d', 17},
  688. {'e', 17},
  689. {'f', 9},
  690. {'g', 17},
  691. {'h', 17},
  692. {'i', 8},
  693. {'j', 8},
  694. {'k', 17},
  695. {'l', 8},
  696. {'m', 27},
  697. {'n', 17},
  698. {'o', 17},
  699. {'p', 17},
  700. {'q', 17},
  701. {'r', 10},
  702. {'s', 17},
  703. {'t', 9},
  704. {'u', 17},
  705. {'v', 15},
  706. {'w', 27},
  707. {'x', 15},
  708. {'y', 17},
  709. {'z', 16}
  710.     };
  711.         #endregion
  712.  
  713.     public static int GetWordWidth(string word)
  714.     {
  715.         int wordWidth = 0;
  716.         foreach(char c in word)
  717.         {
  718.             int thisWidth = 0;
  719.             bool contains = _charWidths.TryGetValue(c, out thisWidth);
  720.             if (!contains)
  721.                 thisWidth = monospaceCharWidth; //conservative estimate
  722.  
  723.             wordWidth += thisWidth;
  724.         }
  725.         return wordWidth;
  726.     }
  727.  
  728.     public static string WrapText(string text, float fontSize, float pixelWidth = adjustedPixelWidth)
  729.     {
  730.         textSB.Clear();
  731.         var words = text.Split(' ');
  732.         var screenWidth = (pixelWidth / fontSize);
  733.         int currentLineWidth = 0;
  734.         foreach (var word in words)
  735.         {
  736.             if (currentLineWidth == 0)
  737.             {
  738.                 textSB.Append($"{word}");
  739.                 currentLineWidth += GetWordWidth(word);
  740.                 continue;
  741.             }
  742.  
  743.             currentLineWidth += spaceWidth + GetWordWidth(word);
  744.             if (currentLineWidth > screenWidth) //new line
  745.             {
  746.                 currentLineWidth = GetWordWidth(word);
  747.                 textSB.Append($"\n{word}");
  748.             }
  749.             else
  750.             {
  751.                 textSB.Append($" {word}");
  752.             }
  753.         }
  754.  
  755.         return textSB.ToString();
  756.     }
  757.     #endregion
  758.  
  759.     #region Monospace
  760.     public static float GetMinimumFontSizeMonospace(int textCharacters)
  761.     {
  762.         var pixelWidth = textCharacters * monospaceCharWidth;
  763.         return adjustedPixelWidth / pixelWidth;
  764.     }
  765.  
  766.     public static string WrapTextMonospace(string text, float fontSize)
  767.     {
  768.         textSB.Clear();
  769.         var words = text.Split(' ');
  770.         var screenWidth = (adjustedPixelWidth / fontSize);
  771.         int currentLineWidth = 0;
  772.         foreach (var word in words)
  773.         {
  774.             if (currentLineWidth == 0)
  775.             {
  776.                 textSB.Append($"{word}");
  777.                 currentLineWidth += word.Length * monospaceCharWidth;
  778.                 continue;
  779.             }
  780.  
  781.             currentLineWidth += (1 + word.Length) * monospaceCharWidth;
  782.             if (currentLineWidth > screenWidth) //new line
  783.             {
  784.                 currentLineWidth = word.Length * monospaceCharWidth;
  785.                 textSB.Append($"\n{word}");
  786.             }
  787.             else
  788.             {
  789.                 textSB.Append($" {word}");
  790.             }
  791.  
  792.         }
  793.         return textSB.ToString();
  794.     }
  795.  
  796.     public static string CenterTextMonospace(string wrappedText, float fontSize)
  797.     {
  798.         textSB.Clear();
  799.         var lines = wrappedText.Split('\n');
  800.         var screenWidth = (adjustedPixelWidth / fontSize);
  801.         var maxCharsPerLine = Math.Floor(screenWidth / monospaceCharWidth);
  802.  
  803.         foreach (var line in lines)
  804.         {
  805.             var trimmedLine = line.Trim();
  806.             var charCount = trimmedLine.Length;
  807.             var diff = maxCharsPerLine - charCount;
  808.             var halfDiff = (int)Math.Max(diff / 2, 0);
  809.             textSB.Append(new string(' ', halfDiff)).Append(trimmedLine).Append("\n");
  810.         }
  811.         return textSB.ToString();
  812.     }
  813.  
  814.     public static string RightJustifyMonospace(string wrappedText, float fontSize)
  815.     {
  816.         textSB.Clear();
  817.         var lines = wrappedText.Split('\n');
  818.         var screenWidth = (adjustedPixelWidth / fontSize);
  819.         var maxCharsPerLine = (int)Math.Floor(screenWidth / monospaceCharWidth);
  820.  
  821.         foreach (var line in lines)
  822.         {
  823.             var trimmedLine = line.Trim();
  824.             var charCount = trimmedLine.Length;
  825.             var diff = maxCharsPerLine - charCount;
  826.             diff = (int)Math.Max(0, diff);
  827.             textSB.Append(new string(' ', diff)).Append(trimmedLine).Append("\n");
  828.         }
  829.         return textSB.ToString();
  830.     }
  831.     #endregion
  832. }
  833.  
  834.  
  835. /*
  836. /// WHAT'S CHANGED? ///
  837. * Ignore offgrid gyros
  838. * Fixed angles spazzing out
  839. * Added yaw control while safety override is active - v23
  840. * Removed the need for a name tag for the control seat - v24
  841. * Added gyro exclude name tag - v25
  842. */
Advertisement
Add Comment
Please, Sign In to add comment