1. This forum is obsolete and read-only. Feel free to contact us at support.keenswh.com

3-Axis Gravity Drive with WASD Movement v2.0

Discussion in 'Programming Released Codes' started by BurninSun, Jun 24, 2016.

Thread Status:
This last post in this thread was made more than 31 days old.
  1. BurninSun

    BurninSun Trainee Engineer

    Messages:
    16
    Finally got around to updating this code. It allows for a gravity drive to be run on all 3 axes just by sitting down in a flight seat and steering like normal.

    With this update, I've made the configuration automatic, including setting up gravity generator field sizes to the minimum necessary to encompass all of the included mass blocks. It can also now bring a ship to a complete stop most of the time instead of the final wobble seen by most other gravity drive scripts. The only exception is on very small / fast ships it will occasionally be unable to stop without a nudge from the player.

    Workshop Link: https://steamcommunity.com/sharedfiles/filedetails/?id=709442667

    I'll post an example ship sometime soon and update this post when that happens.
    Example ships posted:
    Space efficient
    https://steamcommunity.com/sharedfiles/filedetails/?id=710088703

    Power Efficient
    https://steamcommunity.com/sharedfiles/filedetails/?id=710085841

    Small Ship
    https://steamcommunity.com/sharedfiles/filedetails/?id=710087426

    Setup instructions are included in the code.

    Code:
    /*
      3-axis Gravity Drive v2.0
    
      Pilot gravity driven ship in full 3-axis using standard WASD movement.
    
      Setup:
      Place 6 small ion thrusters anywhere on the ship, each facing a different direction.
      Place various gravity generators facing whichever desired direction.
      Place various artificial mass blocks.
      Change the name of all of the above items to include the "[GD]" tag (configurable in script).
      Place a programming block containing this code.
      Place a timer block. Set timer to 1 second and set its actions to "Trigger Now" on itself, "Start" on itself,
      as well as "Run" the programming block (DO NOT choose "Run with default argument". Instead just choose
      "Run" and leave the field blank and hit ok.)
      On the programming block, manually run once with the argument of "Reset".
      This will automatically set the field strength of all of your gravity generators to exactly encompass the artificial
      mass blocks.
      Start the timer and you're good to go.
    
      The script can handle docking / undocking via both connectors and merge blocks. It can also handle the
      destruction of any participating block. However, if any connectors, merge blocks, power sources, gravity
      generators,artificial masses, or flight seats are built, rerun the program block manually with the argument
      of "Reset" to rescan the ship for any newly added components.
    
      To disable the system, run the programming block with the argument of "Stop". To restart it from here, simply
      turn the programming block back on.
    
      If docking 2 gravity driven ships together, one should have its drive disabled as per above.
    
      The script detects dampeners attempting to come to a stop and will bring the ship to a complete stop most of
      the time. Occasionally, very small/fast gravity driven ships won't come to a complete stop. This can usually
      be fixed by moving the ship a small amount.
    
      The ideal ratio of gravity generators to artificial mass blocks depends on ship requirements. For space
      efficiency, use a 1:1 ratio. For power efficiency, it depends on how far apart the components are placed
      but a reasonable design will have around 10:1 gravity generators to artificial mass ratio.
    
    */
    
    const string Tag = "[GD]";     // use this tag on all gravity generators, artificial mass, and the 6 directional thrusters used for the drive
    
    const float StopSpeed = 2.5f;   // under this speed (per axis) the script will take over dampener control to attempt to stop
    
    //*******************
    
    const float BlockSize = 2.5f;           // size of blocks in meters
    const float BlockSizeAdditional = 5.01f;     // size of grav gen plus art mass blocks needed for gravity field
    
    const float GravMax = 9.81f;           // max grav gens can be set to
    
    const float ThrusterPower = 3.36f;         // power usage range considered valid for thruster at full output
    const float ThrusterErrorFactor = 0.01f;
    const float ThrusterPowerLow = ThrusterPower - ThrusterErrorFactor;
    const float ThrusterPowerHigh = ThrusterPower + ThrusterErrorFactor;
    
    const float ThrusterMinOverride = 1.001f;     // minimum override recognized as actually overriding
    
    const int NumAxes = 3;
    const int NumDirections = 2;
    
    int CurrAxis = 0;         // which axis are we currently working with
    bool BaseDirection = true;     // are we on the first direction of this axis
    int AxesEnabled = 0;       // count of currently enabled axes
    float BasePowerUsage;       // power usage on first direction of current axis
    
    float[] GravApplied = new float[NumAxes] {0f, 0f, 0f};         // amount of gravity currently applied per axis
    static readonly Vector3[] VectorToDirection = new Vector3[NumAxes]    // converts Vector3D handedness to Base6Directions
       {Vector3.Backward, Vector3.Right, Vector3.Down};
    
    bool[] ManualStop = new bool[NumAxes] {false, false, false};     // enable manual dampeners on this axis
    const float ManualStopFactor = 0.5f;                 // scaling factor for acceleration when manually dampening
    const float ManualStopIncrease = 1+ManualStopFactor;         // these are specifically not inverses of each other to allow variations when cycling
    const float ManualStopDecrease = -(1-ManualStopFactor);         // negated to flip acceleration direction at the same time
    
    List<IMyTerminalBlock> list = new List<IMyTerminalBlock>();       // generic list, never gets saved
    
    List<IMyTerminalBlock> MassBlocks = new List<IMyTerminalBlock>();
    List<IMyTerminalBlock>[,] GravGenBlocksPerDirection = new List<IMyTerminalBlock>[NumAxes, NumDirections];
    IMyThrust[,] ThrustBlockPerDirection = new IMyThrust[NumAxes, NumDirections];
    
    List<IMyBatteryBlock> BatteryBlocks = new List<IMyBatteryBlock>();
    List<IMyReactor> ReactorBlocks = new List<IMyReactor>();
    List<IMySolarPanel> SolarPanelBlocks = new List<IMySolarPanel>();
    
    List<IMyShipController> ShipControllerBlocks = new List<IMyShipController>();   // needed for velocity reading
    
    List<IMyShipConnector>[] ConnectorBlocks = new List<IMyShipConnector>[2];     // lists per connection state
    List<IMyShipMergeBlock>[] MergeBlocks = new List<IMyShipMergeBlock>[2];
    
    
    void Init() {
       // get mass blocks
       GridTerminalSystem.GetBlocksOfType<IMyVirtualMass>(MassBlocks, b => {
         if (b.CubeGrid == Me.CubeGrid && b.CustomName.Contains(Tag)) {
           b.ApplyAction("OnOff_Off");
           return true;
         }
         return false;
       });
    
       // allocate lists for Gravity Generators
       for (int i = 0; i < NumAxes; i++) {
         for (int j = 0; j < NumDirections; j++) {
           GravGenBlocksPerDirection[i, j] = new List<IMyTerminalBlock>();
         }
       }
       // get Gravity Generators organized by axis and direction
       GridTerminalSystem.GetBlocksOfType<IMyGravityGenerator>(list, b => {
         if (b.CubeGrid == Me.CubeGrid && b.CustomName.Contains(Tag)) {
           // We actually want "Orientation.Down" here but that doesn't exist. Instead of flipping the vector
           // we just get "Orientation.Forward" on the IMyThrust blocks since we really want "Orientation.Backward"
           // on that one. Both vectors are therefore reversed and the script works as intended.
           Base6Directions.Direction orientation = b.Orientation.Up;
           Base6Directions.Axis axis = Base6Directions.GetAxis(orientation);
           GravGenBlocksPerDirection[(int)axis, (orientation == Base6Directions.GetBaseAxisDirection(axis) ? 0 : 1)].Add(b);
           b.ApplyAction("OnOff_Off");
         }
         return false;
       });
       // get 1 thruster per axis and direction
       GridTerminalSystem.GetBlocksOfType<IMyThrust>(list, b => {
         if (b.CubeGrid == Me.CubeGrid && b.CustomName.Contains(Tag)) {
           // We really want "Orientation.Backward" here. See above IMyGravityGenerator above for explanation.
           Base6Directions.Direction orientation = b.Orientation.Forward;
           Base6Directions.Axis axis = Base6Directions.GetAxis(orientation);
           ThrustBlockPerDirection[(int)axis, (orientation == Base6Directions.GetBaseAxisDirection(axis) ? 0 : 1)] = (b as IMyThrust);
           b.SetValueFloat("Override", 0f);
           (b as IMyThrust).RequestEnable(false);
         }
         return false;
       });
       // get ship controllers
       GridTerminalSystem.GetBlocksOfType<IMyShipController>(list, b => (b.CubeGrid == Me.CubeGrid));
       ShipControllerBlocks = list.ConvertAll(b => (b as IMyShipController));
    
       // reset states
       for (int i = 0; i < NumAxes; i++) {
         GravApplied[i] = 0f;
         ManualStop[i] = false;
       }
       AxesEnabled = 0;
       CurrAxis = 0;
       BaseDirection = true;
    }
    
    void SetupGravity() {
       // get bounding box of mass blocks
       Vector3I min = Vector3I.MaxValue;
       Vector3I max = Vector3I.MinValue;
       MassBlocks.ForEach(b => {
         min = Vector3I.Min(min, b.Position);
         max = Vector3I.Max(max, b.Position);
       });
       // set gravity generator fields to encompass mass blocks
       for (int i = 0; i < NumAxes; i++) {
         for (int j = 0; j < NumDirections; j++) {
           GravGenBlocksPerDirection[i, j].ForEach(b => {
             Vector3I dist = Vector3I.Max(Vector3I.Abs(b.Position - min), Vector3I.Abs(b.Position - max));
             (b as IMyGravityGenerator).SetValue("Width", BlockSizeAdditional+BlockSize*dist.AxisValue(Base6Directions.GetAxis(b.Orientation.TransformDirection(Base6Directions.Direction.Left))));
             (b as IMyGravityGenerator).SetValue("Height", BlockSizeAdditional+BlockSize*dist.AxisValue(Base6Directions.GetAxis(b.Orientation.TransformDirection(Base6Directions.Direction.Up))));
             (b as IMyGravityGenerator).SetValue("Depth", BlockSizeAdditional+BlockSize*dist.AxisValue(Base6Directions.GetAxis(b.Orientation.TransformDirection(Base6Directions.Direction.Forward))));
           });
         }
       }
    }
    
    IMyShipMergeBlock OtherMergeBlock(IMyShipMergeBlock b) {
       // lack of built in check for merge block connections
       // https://forum.keenswh.com/threads/merge-block-lock-state-checking.7378572/#post-1286926979
       Matrix mat;
       b.Orientation.GetMatrix(out mat);
       Vector3I right1 = new Vector3I(mat.Right);
       IMySlimBlock slim = b.CubeGrid.GetCubeBlock(b.Position+right1);
       if (slim == null) return null;
       IMyShipMergeBlock merge2 = slim.FatBlock as IMyShipMergeBlock;
       if (merge2 == null) return null;
       merge2.Orientation.GetMatrix(out mat);
       Vector3I right2 = new Vector3I(mat.Right);
       if (right1 == -right2 && merge2.IsFunctional) return merge2;
       return null;
    }
    
    void SetupConnections() {
       // sets connector and battery lists
       ConnectorBlocks[0] = new List<IMyShipConnector>();
       ConnectorBlocks[1] = new List<IMyShipConnector>();
       MergeBlocks[0] = new List<IMyShipMergeBlock>();
       MergeBlocks[1] = new List<IMyShipMergeBlock>();
       GridTerminalSystem.GetBlocksOfType<IMyShipConnector>(list, b => {
         if (b.IsFunctional) {
           IMyShipConnector block = (b as IMyShipConnector);
           // don't need to check both sides for connection
           if (block.IsConnected) {
             if (!ConnectorBlocks[1].Contains(block.OtherConnector)) {
               ConnectorBlocks[1].Add(block);
             }
           } else {
             ConnectorBlocks[0].Add(block);
           }
         }
         return false;
       });
       GridTerminalSystem.GetBlocksOfType<IMyShipMergeBlock>(list, b => {
         if (b.IsFunctional) {
           IMyShipMergeBlock block = (b as IMyShipMergeBlock);
           // don't need to check both sides for connection
           IMyShipMergeBlock merge2 = OtherMergeBlock(block);
           if (merge2 != null) {
             if (!MergeBlocks[1].Contains(merge2)) {
               MergeBlocks[1].Add(block);
             }
           } else {
             MergeBlocks[0].Add(block);
           }
         }
         return false;
       });
    
       // get power production blocks
       GridTerminalSystem.GetBlocksOfType<IMyBatteryBlock>(list);
       BatteryBlocks = list.ConvertAll(b => (b as IMyBatteryBlock));
       GridTerminalSystem.GetBlocksOfType<IMyReactor>(list);
       ReactorBlocks = list.ConvertAll(b => (b as IMyReactor));
       GridTerminalSystem.GetBlocksOfType<IMySolarPanel>(list);
       SolarPanelBlocks = list.ConvertAll(b => (b as IMySolarPanel));
    }
    
    bool CheckConnections() {
       // check that connection states have not changed
       for (int i = 0; i < ConnectorBlocks[0].Count; i++) {
         if (!ConnectorBlocks[0][i].IsFunctional || ConnectorBlocks[0][i].IsConnected) return true;
       }
       for (int i = 0; i < ConnectorBlocks[1].Count; i++) {
         if (!ConnectorBlocks[1][i].IsFunctional || !ConnectorBlocks[1][i].IsConnected) return true;
       }
       for (int i = 0; i < MergeBlocks[0].Count; i++) {
         if (!MergeBlocks[0][i].IsFunctional || OtherMergeBlock(MergeBlocks[0][i]) != null) return true;
       }
       for (int i = 0; i < MergeBlocks[1].Count; i++) {
         if (!MergeBlocks[1][i].IsFunctional || OtherMergeBlock(MergeBlocks[1][i]) == null) return true;
       }
       return false;
    }
    
    public Program() {
       Init();
       SetupConnections();
    }
    
    public void Save() {
    }
    
    void Main(string argument) {
       if (argument.Length > 0) {
         switch (argument.ToUpper()) {
           case "RESET": {
             Init();
             SetupGravity();
             SetupConnections();
             break;
           }
           case "STOP": {
             Init();
             (Me as IMyFunctionalBlock).ApplyAction("OnOff_Off");
             break;
           }
         }
         return;
       }
    
       // check connectors if anything has been added or removed
       if (CheckConnections()) SetupConnections();
    
       // get power usage
       float powerUsage = 0;
       for (int i = BatteryBlocks.Count-1; i >= 0; i--) {
         if (BatteryBlocks[i].IsFunctional) {
           powerUsage += BatteryBlocks[i].CurrentOutput;
         } else {
           BatteryBlocks.RemoveAt(i);
         }
       }
       for (int i = ReactorBlocks.Count-1; i >= 0; i--) {
         if (ReactorBlocks[i].IsFunctional) {
           powerUsage += ReactorBlocks[i].CurrentOutput;
         } else {
           ReactorBlocks.RemoveAt(i);
         }
       }
       for (int i = SolarPanelBlocks.Count-1; i >= 0; i--) {
         if (SolarPanelBlocks[i].IsFunctional) {
           powerUsage += SolarPanelBlocks[i].CurrentOutput;
         } else {
           SolarPanelBlocks.RemoveAt(i);
         }
       }
    
       if (BaseDirection) {
         // first call on this axis
         BasePowerUsage = powerUsage;
         BaseDirection = false;
    
         // switch active thruster
         if (ManualStop[CurrAxis]) {
           ThrustBlockPerDirection[CurrAxis, 0].SetValueFloat("Override", ThrusterMinOverride);
           ThrustBlockPerDirection[CurrAxis, 1].SetValueFloat("Override", 0);
         } else {
           ThrustBlockPerDirection[CurrAxis, 0].RequestEnable(false);
           ThrustBlockPerDirection[CurrAxis, 1].RequestEnable(true);
         }
       } else {
         // second call on this axis
         // disable this axis thrusters
         if (ManualStop[CurrAxis]) {
           ThrustBlockPerDirection[CurrAxis, 0].SetValueFloat("Override", 0);
           ThrustBlockPerDirection[CurrAxis, 0].RequestEnable(false);
           ThrustBlockPerDirection[CurrAxis, 1].RequestEnable(false);
         } else {
           ThrustBlockPerDirection[CurrAxis, 1].RequestEnable(false);
         }
    
         // figure out acceleration
         float accel;
         float power = Math.Abs(powerUsage-BasePowerUsage);
         if (ManualStop[CurrAxis]) {
           // **why doesn't this need to be adjusted by the power used due to override?
           if (power <= ThrusterPowerHigh && power >= ThrusterPowerLow) {
             // player is accelerating
             ManualStop[CurrAxis] = false;
             accel = (powerUsage > BasePowerUsage ? GravMax : -GravMax);
           } else {
             // adjust acceleration strength if speed is same direction as acceleration or not
             int ind = ShipControllerBlocks.FindIndex(b => (b.IsFunctional));
             if (ind > 0) {
               ShipControllerBlocks.RemoveAll(b => (!b.IsFunctional));
               ind = ShipControllerBlocks.FindIndex(b => (b.IsFunctional));
             }
             if (ind == 0) {
               Vector3D worldVelocity = ShipControllerBlocks[0].GetShipVelocities().LinearVelocity;
               double speed = Vector3D.Transform(worldVelocity, MatrixD.Transpose(Me.CubeGrid.WorldMatrix.GetOrientation())).Dot(VectorToDirection[CurrAxis]);
               int speedDir = Math.Sign(speed);
               if (speedDir == 0) {
                 accel = 0;
               } else {
                 if (Math.Sign(GravApplied[CurrAxis]) == speedDir) {
                   accel = MathHelper.Clamp(GravApplied[CurrAxis]*ManualStopIncrease, -GravMax, GravMax);
                 } else {
                   accel = GravApplied[CurrAxis]*ManualStopDecrease;
                 }
               }
             } else { // don't have a block to check speed with
               accel = 0f;
               ManualStop[CurrAxis] = false;
             }
           }
         } else { // not manually stopping
           // either player is accelerating or not
           if (power <= ThrusterPowerHigh && power >= ThrusterPowerLow) {
             accel = (powerUsage > BasePowerUsage ? GravMax : -GravMax);
           } else {
             accel = 0f;
           }
         }
    
         // set grav drive per needed acceleration
         if (accel != 0) {
           if (GravApplied[CurrAxis] != accel) {
             // changed acceleration direction
             if (GravApplied[CurrAxis] == 0f) {
               // turn on gravity generators for this axis
               if (AxesEnabled++ == 0) {
                 for (int i = MassBlocks.Count-1; i >= 0; i--) {
                   if (MassBlocks[i].IsFunctional) {
                     MassBlocks[i].ApplyAction("OnOff_On");
                   } else {
                     MassBlocks.RemoveAt(i);
                   }
                 }
               }
               for (int i = GravGenBlocksPerDirection[CurrAxis, 0].Count-1; i >= 0; i--) {
                 if (GravGenBlocksPerDirection[CurrAxis, 0][i].IsFunctional) {
                   GravGenBlocksPerDirection[CurrAxis, 0][i].ApplyAction("OnOff_On");
                   GravGenBlocksPerDirection[CurrAxis, 0][i].SetValue("Gravity", -accel);
                 } else {
                   GravGenBlocksPerDirection[CurrAxis, 0].RemoveAt(i);
                 }
               }
               for (int i = GravGenBlocksPerDirection[CurrAxis, 1].Count-1; i >= 0; i--) {
                 if (GravGenBlocksPerDirection[CurrAxis, 1][i].IsFunctional) {
                   GravGenBlocksPerDirection[CurrAxis, 1][i].ApplyAction("OnOff_On");
                   GravGenBlocksPerDirection[CurrAxis, 1][i].SetValue("Gravity", accel);
                 } else {
                   GravGenBlocksPerDirection[CurrAxis, 1].RemoveAt(i);
                 }
               }
             } else { // GravApplied[CurrAxis] != 0
               // acceleration direction got flipped, check if we're trying to stop
               if (!ManualStop[CurrAxis] && Math.Sign(GravApplied[CurrAxis]) != Math.Sign(accel)) {
                 int ind = ShipControllerBlocks.FindIndex(b => (b.IsFunctional));
                 if (ind > 0) {
                   ShipControllerBlocks.RemoveAll(b => (!b.IsFunctional));
                   ind = ShipControllerBlocks.FindIndex(b => (b.IsFunctional));
                 }
                 if (ind == 0) {
                   Vector3D worldVelocity = ShipControllerBlocks[0].GetShipVelocities().LinearVelocity;
                   double speed = Vector3D.Transform(worldVelocity, MatrixD.Transpose(Me.CubeGrid.WorldMatrix.GetOrientation())).Dot(VectorToDirection[CurrAxis]);
                   if (Math.Abs(speed) <= StopSpeed) {
                     ManualStop[CurrAxis] = true;
                   }
                 }
               }
               // set gravity generators
               for (int i = GravGenBlocksPerDirection[CurrAxis, 0].Count-1; i >= 0; i--) {
                 if (GravGenBlocksPerDirection[CurrAxis, 0][i].IsFunctional) {
                   GravGenBlocksPerDirection[CurrAxis, 0][i].SetValue("Gravity", -accel);
                 } else {
                   GravGenBlocksPerDirection[CurrAxis, 0].RemoveAt(i);
                 }
               }
               for (int i = GravGenBlocksPerDirection[CurrAxis, 1].Count-1; i >= 0; i--) {
                 if (GravGenBlocksPerDirection[CurrAxis, 1][i].IsFunctional) {
                   GravGenBlocksPerDirection[CurrAxis, 1][i].SetValue("Gravity", accel);
                 } else {
                   GravGenBlocksPerDirection[CurrAxis, 1].RemoveAt(i);
                 }
               }
             }
             GravApplied[CurrAxis] = accel;
           }
         } else { // accel == 0
           // stopped
           if (GravApplied[CurrAxis] != 0f) {
             // turn off gravity generators
             GravApplied[CurrAxis] = 0f;
             if (--AxesEnabled == 0) {
               for (int i = MassBlocks.Count-1; i >= 0; i--) {
                 if (MassBlocks[i].IsFunctional) {
                   MassBlocks[i].ApplyAction("OnOff_Off");
                 } else {
                   MassBlocks.RemoveAt(i);
                 }
               }
             }
             for (int i = GravGenBlocksPerDirection[CurrAxis, 0].Count-1; i >= 0; i--) {
               if (GravGenBlocksPerDirection[CurrAxis, 0][i].IsFunctional) {
                 GravGenBlocksPerDirection[CurrAxis, 0][i].ApplyAction("OnOff_Off");
               } else {
                 GravGenBlocksPerDirection[CurrAxis, 0].RemoveAt(i);
               }
             }
             for (int i = GravGenBlocksPerDirection[CurrAxis, 1].Count-1; i >= 0; i--) {
               if (GravGenBlocksPerDirection[CurrAxis, 1][i].IsFunctional) {
                 GravGenBlocksPerDirection[CurrAxis, 1][i].ApplyAction("OnOff_Off");
               } else {
                 GravGenBlocksPerDirection[CurrAxis, 1].RemoveAt(i);
               }
             }
             ManualStop[CurrAxis] = false;
           }
         }
         // go to next axis
         if (++CurrAxis == NumAxes) CurrAxis = 0;
         BaseDirection = true;
    
         // enable thrusters for next axis
         if (ManualStop[CurrAxis]) {
           ThrustBlockPerDirection[CurrAxis, 0].RequestEnable(true);
           ThrustBlockPerDirection[CurrAxis, 1].RequestEnable(true);
           ThrustBlockPerDirection[CurrAxis, 1].SetValueFloat("Override", ThrusterMinOverride);
         } else {
           ThrustBlockPerDirection[CurrAxis, 0].RequestEnable(true);
         }
       }
    }
    
     
    Last edited: Jun 25, 2016
    • Like Like x 1
Thread Status:
This last post in this thread was made more than 31 days old.