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

[WIP] Ackermann Steering - Suspension Block Controller

Discussion in 'Programming Released Codes' started by plaYer2k, Nov 6, 2015.

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

    plaYer2k Master Engineer

    Messages:
    3,160
    People know the issue. You want to make a nice car but the suspension wheels dont work as they are supposed to.
    There mainy are two problems with them right now this ingame program tries to fix.



    Problem

    1) Wheel Locking
    You made a car but when steering the wheels lock and you are unable to accelerate forward or backward. That is especially prominent when the friction and/or steering angles are very high.

    This image shall demonstrate the issue. A rather simple setup with four wheels that all got the same steering angle.
    Each wheels roll-axis has been highlighted in red (rough estimation).

    [​IMG]

    The probelm here is that the wheels try to move each around different focus points.
    With high enough steering angle, these points lie very far apart from each other.
    With high enough friction the "resistance" against breaking free from this effect is very high.

    2) Setting up angles for multiple wheels
    Furthermore setting all wheels up to a proper angle so you got smooth turns is rather hard or just tedious with large setups of suspension wheels. Think of a high payload car with 30, 50 or even 70 wheels (yes i had such cars already). Setting every single wheel up in these setups is extremely tedious.


    Solution

    Ackermann Steering - Suspension Block Controller

    Let a program do all the SteerAngle, SteerSpeed and ReturnSpeed settings for you while maintaining a good focus point for all wheels on the left and right!

    Features:
    • support for any amount of wheels (tested with up to 50 wheels)
    • support for any kind of arrangement as long as the wheels point to the left/right in forward direction.
    • automatically change each wheels:
      • MaxSteerAngle - the max angle towards which a wheel steer
      • SteerSpeed - the speed at which the wheel steers towards the max angle when actively steering (left/right press)
      • SteerReturnSpeed - the speed at which the wheel steers towards the center again when no steer input is present anymore
    • low resistance / high liquidity when doing turns with high angle and high friction
    • no big setup costs for wheel arrangements
    • very easy to embed into your current program due to OOP
    • set a suspension wheels max steer angle with adding "MaxSteerAngle=X" to the blocks name where X is the angle in radians like when pressing ctrl+click within the terminals angle slider

    Setup

    All you need to set this system up is:
    • 1 programmable block
    • 1 timer block
    • 1 anchor block (i usually use remote controls here)
    • any amount of wheels
    The programmable block needs to load the program.

    The timer should be setup in a once-per-update manner. I suggest the following order:
    1. Start self
    2. Run programmable block
    3. Trigger now self

    The anchor block has be to positioned correctly. The blocks orientation has to be the same as the one of the cars local movement. Thus forward has to be forward, up has to be up and right has to be right.

    The picture below should visually demonstrate the suggested setup.

    [​IMG]


    Though what does it look like you ask?

    Well here is one simple example for a sane and normal car.

    [​IMG]


    This example demonstrates that even very arbitrary wheel setups work well.

    [​IMG]

    Setup example video (release 1)


    TODO:
    • Document the code and generalize it a bit more.
    • Allow for degree values with the MaxSteerAngle parameter within the blocks name.
    • There is still a small offset missing for wheels as the position i take is not exactly where the wheel steers around.
    • Add gyro support to prevent flipping over. Though that also requires me to "turn left/right" through the gyro as an override gyro would otherwise hinder turning through steering.
    • Think of other wheel applications like "lean into a curve" or "jump".

    Code


    Furthermore i sadly can not upload any videos because our ISP decided that some internal mishaps on their end should happen so they canceled our internet to the last weekend. Thus i am stuck on 6 kB/s through my phone (again *sigh*).
    I got a few simple example videos prepared though i can not upload them. However as soon as i uploaded the code people will see how nicely they work now.


    Enjoy

    Known bugs:
    • The center offset doesnt work properly.

    Change Log:
    • 2015-11-13
      • Added: HowTo video based on release 1
    • 2015-11-09
      • Fixed: Error handling when a wheel is not present anymore.
    • 2015-11-06
      • Initial release
     
    Last edited: Nov 13, 2015
    • Like Like x 12
  2. Xellon

    Xellon Trainee Engineer

    Messages:
    66
    Nice, exacly what I needed for one of my builds! It's a rather Large Setup currently in the making for planetary Purposes, and I already noticed that making turns with it would be a total mess.
    Could you please upload it to the Workshop so I can subscribe? It's easier to find again later and to stay up-to-date!
     
  3. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Sadly no, i cant.
    Thanks to our ISP deciding that we are no longer worthy to have internet, i am stuck on ~6kB/s.

    Thus i can not update SE and subsequently am unable to publish anything to the workshop.
     
  4. Xellon

    Xellon Trainee Engineer

    Messages:
    66
    Oh scrap, that sucks!
    Well, if you want, I could upload this to the Workshop for you, with all the "That's not mine, this Guy made it!"-Stuff, of course!
     
  5. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Thanks for the offer though if i would want that, i sure had found a way to do it like that.

    However i dislike that method as it means that as soon as i upload my version, ther are two different branches and people have to subscribe to the new one.
    I prefer to keep it that way right now. Hopefully my laptop will grab some net somewhere to update SE and upload the program.
     
  6. Xellon

    Xellon Trainee Engineer

    Messages:
    66
    Time to visit Starbucks, huh? ;)
     
  7. Smokki

    Smokki Trainee Engineer

    Messages:
    77
    Nice job! This is going to be a must have for every single wheeled vehicle.
     
  8. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    I don't really think we have time for a handjob, Joe.

    I hope so. Lets see though :)
     
    Last edited: Nov 6, 2015
  9. Thermonuklear

    Thermonuklear Junior Engineer

    Messages:
    615
    My last thought before I fell asleep last night: "If someone could make a script that keeps those damn wheels aligned..." You deserve a big hug!
     
  10. Merandix

    Merandix Junior Engineer

    Messages:
    520
    It's awesome, but I'm getting two weird things:
    1: the timer block setup you give only triggers once, not sure if this is intended, but why a timer then?
    Unless you mean that *I* have to start the timer manually, 1st slot Run the programmable block (with default parameter?), 2nd slot trigger now, because yes, then it keeps repeating. It really looks as if you mean: 1 start, 2 run, 3 trigger now.
    2: I get 2 wheels on a 6 wheel setup which hardly turn. Does this have to do with remote-block positioning?

    And finally a question; is there a possibility to make the turning radius larger using this? Because I feel like the wheels lock up if they turn too much :woot: (but after today, and your advice earlier, that may just be me screwing up on wheel-settings again).
     
  11. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    That is odd and shouldnt happen.

    Whenever i used "start timer > run PB > trigger now timer" it worked flawlessly, triggered the timer every update and even started again after a reload of the world.


    Yes absolutely. The anchor block (in your case a remote control, like my example) determines the local coordinates that are used for all angles.

    Here is a small overview.


    [​IMG]

    As you can see, the anchor block (RC) is in the middle where everything starts.

    From there one there is the option for a local offset to change the blue point which is used as center for all further calculations. This center offset is by default (0, 0, 0) but you can change that within the constructors parameter in line 13. Just keep the local grid coordinates in mind. Forward actually is -Z and thus backwards is +Z. To the right is +X and to the left -X. Up and down are +Y and -Y but they are not relevant for anything right now.

    The focus point to the left and right of the center is always along the "steer focus axis" which is the y=0, z=0 axis.

    Thus if you have a remote or any other anchor block placed where you dont want your steering center around, use the offset given and you can change the actual steering center.
    This is also important for when your anchor block is far to a side like the right and you need to change the offset to the left.
    The offsets values are in meter and explicitely not in blocks.

    Lets say for example your large block car is 5 blocks wide and 5 blocks long.
    The anchor block is placed at the top right corner and you want it to sit in the middle-bottom.
    One good offset for that would be (-5, 0, 7.5) and thus you move the position of the center relative to the anchor block two blocks to the left and three blocks backwards.

    You could add a parameter to the suspension wheel blocks name like "MaxSteerAngle=0.3" and thus limit the max angle that one wheel can steer. That value is in radians and 0.3rad equals roughly 17°.
    I max add a method to directly add values in degree if there is a °-sign at the end of the string.


    I hope that helps.


    Edit:
    Oh fudge i just noticed that the center offset is buggy. Z is reversed and X/Y dont seem to have an impact. First entry to the bug list <3

    Edit2: Alright i figured out what the issue is. The offset does work as expected, in a certain range.
    The issue is that the SE internal steering of course drives with the suspension wheels relatie position to the ship controller driving them. However when you set an offset in such a way that the relatie position of the center i use shifts so far that the sign of one of the wheel positions changes (i.e. wheels that have been above the steering axis are now below) they had to invert steering.
    Now to properly check that, the program has to know which ship controller is using them to get the relative position in respect to that block.

    That however means that instead of an anchor block i might actually need to use a ship controller (cockpit, remote control) and assume that this block is the single input for the wheels. Thus multiple ship controller (i.e. double cockpits) are not possible anymore in such a case.

    The world would be so much easier if we had direct access to the control inputs (2x Vector3) aswell as the ability to control suspension wheels through ingame programming.

    TL;DR: Do not pick an offset that moves so far that any wheel that previously was on one side (up/down/left/right) is on another afterwards. Otherwise you manually need to compensate the steering with "invert steering" once.
     
    Last edited: Nov 8, 2015
    • Like Like x 1
  12. Merandix

    Merandix Junior Engineer

    Messages:
    520
    I suspect this has to do with friction settings, but I don't know how to solve it...

    I get very jerky motion when I take minimum radius turns... how to prevent it... because I'm not really good at tweaking all those settings yet <.<
     
  13. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Yeah you might have a small block car and additionally the wheels are farther apart in left/right direction than in forward/backward direction.
    If so, that issue is caused by the additional wheel steering axis offset each wheel has. Currently i just used the center of each suspension wheel block as axis for their rotations but it actually is farther apart from there and thus there is still an offset.

    To fix that i started building a dictionary (lookup table) for offsets for each type of wheel, though i never finished it or implemented the offset correction.
    I will do that soonish though. Other RL priorities first though.
     
  14. Merandix

    Merandix Junior Engineer

    Messages:
    520
    Ah, thanks a lot. It's indeed a small block car. As my testing ground isn't big enough for large ones :p

    When planets hit, I'm sure I will use this script a LOT!

    Thanks for sharing it :)
     
  15. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    Interesting, you've also been working on ESC (electronic stability control). :)

    The functions
    • Add gyro support to prevent flipping over. Though that also requires me to "turn left/right" through the gyro as an override gyro would otherwise hinder turning through steering.
    • Think of other wheel applications like "jump".
    are already implemented in my version. Gyro control can still be improved.

    You can download it from the PB of my ExtremeRacer (version 0.5) or the UESC contest vehicles (version 0.6) in block "ESP".
    Feel free to take a look for inspiration and take what you need. The settings are not up to date anymore since gyro power and wheels have changed in 1.107 but the Tick still has excellent handling characteristics, especially on planet surfaces

    Currently implemented in version 0.6 (vehicles Tick and Cricket):
    • reduce the tendency to flip by controlling the friction of the front wheels when turning and the side wheels when sliding
    • reduce the tendency to flip by control of the gyros
    • reduce the maximum steering angle when driving faster (steering in SE is digital, also with joystick). The limits can be set individually for front axles and rear axles
    • adjust the maximum steering angle and steering speeds on the different axles (can be easily observed on the Cricket). That's a cheap version of your Ackermann steering (no different angles for in- and outside, but planned)
    • maximise acceleration and braking by controlling the friction and power of all wheels providing three power modes (just 2 of them used in Tick and Cricket)
    • maximise braking by switching inertia dampeners on (thruster support)
    • keep the attitude when in the air (gyro control when jumping)
    • keep nose or rear down when driving on surface (gyro)
    • adjust suspension parameters for off-road (asteroids, planets) and grids automatically (height can be chosen manually, too)
    • control of the side thrusters according to the situation (straight - turning - sliding)
    • jumping(or better hopping). The Tick jumps ca.4.5m high (suspension = high) / 100+ m (suspension = low) @ 1.0G natural gravity. Observed that I need to increase the jump delay to 50. Then the Tick jumps also 100+m high with suspension height setting = high.
    • Control of artificial masses (switched off during jumping)
    • catlanding for planetary drops (not working in Cricket, gyro pitch is not working somehow)
    • full player control of gyros and thrusters and activation of the inertia dampeners when the handbrake is on (emergency stopping and reorientation), limited flight in space
    • the handbrake is switched on and the vehicle is lowered when the vehicle is not controlled by the pilot anymore (lowering is for easier entering of the Tick but done in both)
    • control of the lights (flashing, braking, reversing)
    • additional vehicle specific functions like hatches, ramps, decoy system, connectors, oxygen tanks, roll bars which have input parameters from the ESC (e.g. speed)
     
    Last edited: Nov 12, 2015
    • Like Like x 1
  16. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    Hmm, I wanted to replace the steering part of my ESC by this one but it's linked to some other functionalities. So I had to modify my code. Cornering with Ackermann steering is clearly smoother but the difference wasn't easy to judge so I did a simple test:
    I accelerated the vehicle to just above 16 km/h, going straight. When the vehicle slowed down to 16.0 km/h I turned to one side letting it roll until the vehicle stopped.

    The one with the Ackermann steering turned approximately 170°, the one without a little bit more than 90°.
    With gyro control this is a bit different. If the gyros are strong they will dominate, especially if the friction of the tyres is low.
     
    • Like Like x 1
  17. [☩] Dealman

    [☩] Dealman Trainee Engineer

    Messages:
    6
    I checked this out, but I couldn't quite get it to work I believe - either that or I didn't really notice any difference. The video wasn't very detailed, you just put blocks - pasted the script and that was it. After reading through this thread, it seems as though the positioning of the blocks matter...? Should the Remote Control block be in the dead center of the vehicle? And it wasn't exactly clear what the timer block did as well, since all you did was click "Trigger Now" and then controlled the vehicle via the Remote Control block. Do we need to do this every time? Don't get me wrong, I'm very interested in the script and appreciate the effort put into it - but that video wasn't very helpful :(

    Does this fix the erratic jumping of ground vehicles? As of now I usually have to set the friction to ~15% in order for it to maneuver in such a way not to flip all over the place. But then it's like you're trying to drive on ice, no matter the surface. It really seems as though they need to re-work the wheels quite a bit :)
     
  18. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    Hi @Dealman,

    yes, the remote control is the reference for the focus point / axis as shown in the picture above. You can move the focus point/axis with the vector in the initialisation (last parameter in new WheelController()) in case the remote control block is not placed where you like to have the focus. It doesn't need to be in the center but it's for sure a good position. If you place it in the center the front and rear wheels will both have the same maximum steering angle. If you place the remote control on the axis of the rear wheels only the front wheels will steer.
    The timer block is just triggering the programmable block so that the script is executed. The setup of the timer block is described in the first picture:
    1) start timer block (itself)
    2) run programmable block
    3) trigger timer block (itself)

    In this first version of the script the maximum steering angle of each wheel is adjusted. The effect is that the wheels will create less resistance when cornering. You can e.g. steer to the right and the vehicle will accelerate without locking the wheels and staying in place. But this was clearly mentioned already at the very beginning of this thread.

    Take a look at the pictures. They are very informative and clearly decribe what this does.

    Currently the script does not change other wheel parameters than the maximum steering angle, so the Springbok behaviour you described is not addressed yet.

    A very good design is the Cheetah from Leonser:
    https://steamcommunity.com/sharedfiles/filedetails/?id=551344822
    Here you can see what's possible without using a programmable block and only using vanilla blocks.

    If you want to see a solution using electronic stability control (=a script solution) then you can download my Zecke scout vehicle:
    https://steamcommunity.com/sharedfiles/filedetails/?id=555160768
    It's a world and the vehicles use mods. So best is to copy the Zecke S2 (requires one mod less than the RB versions), create a blueprint, apply the mods to your planet world and paste it there. Or just watch the video in the discussion session, but, hey, that's boring !
    It's faster and more agile than the Cheetah and has more functions.
     
    Last edited: Nov 24, 2015
  19. Thermonuklear

    Thermonuklear Junior Engineer

    Messages:
    615
    @Lynnux , is your ESC script's functionality dependant on mods? Could you perhaps upload the script to Workshop?
     
  20. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    No, no dependency on mods. The script is not ready for an official upload because there are still too many things which have to be improved/fixed. E.g. in the current version 0.10 I forgot to comment/remove a debug string which is now increased by 8400 characters per second. After some time it causes the GC memory to be increased by 1GB/s (!!!). At 14..15 GB the GC memory will be flushed back to 4GB causing a game freeze of 1.5 seconds here. So every 8 seconds a freeze of the game.
    Seems like I found a weakness/bug of the in-game scripting :eek:

    You can download the vehicle and store the script, though. The PB is named "ESP".

    Zecke with ESC 0.11 is uploaded (bug fixed).
     
    Last edited: Nov 26, 2015
    • Like Like x 1
  21. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    If you want the offset working modify line 82 to this line:
    Code:
     wheelOffset = Vector3D.Transform(wheelOffset-offset, wheelMatrixInv);
    
    ...and remove/comment line 83 75. (<-EDIT)
     
    Last edited: Nov 29, 2015
  22. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Which of the many offsets are you talking about there? The one offset that gets calculated there actually is the additional tiny offset the actual wheel turning axis has that is off the suspension blocks center.
    So when you look onto a suspension block from above you see that the wheel doesnt turn around the axis that is in the middle of the block but off its center. That exactly this line of code corrects.

    As for the actual "local center offset" it might be that you use the negative value of what had to be used. I think i even wrote the wrong example there aswell. Because it is the wheels that move in that direction and not the offset itself. So with an offset of X = +3 the center offset moves to the left and not to the right relative to the wheels because the wheels themself move 3 to the right.
    I wasnt quite sure which way around to use so it is more intuitive for people to use.
     
  23. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    This one:
    I saw that you applied the AnchorOffset to the world coordinates (in the worldMatrix) as it seems. I tried to find an easy way to apply it and just subracted it from the wheel offset before it's rotated into world coordinates.
    But maybe you already fixed the center offset. I assumed it's not working and didn't try I have to admit.

    EDIT: Ok, tested again and without my modification the remote control is always the center also when changing the offset vector.
     
    Last edited: Nov 29, 2015
  24. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    Yes, I see why you are confused. Line 75 is obsolete with my change, not line 83. I confused these two lines when posting here. Sorry, my bad.
     
  25. Tommy

    Tommy Trainee Engineer

    Messages:
    8
    I'm loving this script.

    About stability of the ride, would it be possible to add in a feature that will cut back the steering angle and speed based on velocity?

    SE will keep the same steering angle and steering speed no matter what forward velocity you attain. In real life, you don't jerk your steering wheel like that when you are at speed because you are likely to flip it over or straight out loose control of your car.

    getting a script to simulate this would be awesome, would program it myself if I know any of the language SE uses. :(
     
  26. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Yes it certainly is possible and i wanted to do it myself before.
    The issue though is that i also want to keep the changes per block and thus the informations send through the netcode as low as possible.
    I could easily update every wheels parameter once per tick for best wheel performance but that would lead to a rather large traffic with bigger wheel setups aswell as more people using that script on cars and would subsequently easily congest many peoples connections in multiplayer leading to a worse multiplayer performance. That is exactly why i never did it so far.

    But once i am home again i might continue expanding the script. I have made a few local changes already to fix the oversight with the angle in degree [°] within the MaxSteerAngle.

    Though being back on 6kB/s at home aswell as FO4 releasing, i didnt spend much time in SE the lately :p
     
  27. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    I've been working on that before Christmas but also stopped. Limiting the steering angle dependent on the speed requires that the focus point is recalculated as plaYer2k already mentioned. And this changes the wheel controller class a lot.
    But the steering angle isn't the main problem in case of flipping. I'll add the reduction of the friction of the outer wheels. This will prevent some tendency to flip. But this requires to acquire some vehicle data (mainly the speed vector and wheel geometry: left and right wheels).
    It's not as easy as it looks though I've already done all that in the script of my Zecke vehicle.
     
  28. Tommy

    Tommy Trainee Engineer

    Messages:
    8
    Mmmm yeah that would make it laggy.
    ok instead of doing it every tick, why not change it at certain speeds kind of like a gear shift Example(speed being in Kph):
    speed < 20: no change in steering,
    speed > 20 & < 40: 20% reduction to steering speed and max angle
    speed > 40 & < 50: 40% reduction to steering speed and angle

    So on and so forth with a possible tweak at the top of the script allowing you to set the speeds and reduction factors.
    same functionality where needed without changing it every tick.

    Mmmm I'll have a look at those. does that script of yours also do the steering angle correction that plaYer2K posted here?
     
  29. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    Yes, but it's no "full" Ackermann. But for a vehicle with 4 axles I implemented a similar solution. As you can see further up my solution already has the steering angle limitation dependent on the current speed (simple linear dependency).
    But from what I saw from comparison of the "Cheetah" made by Leonser with my "Zecke" this is not as important as changing the friction of the wheels. This is what makes the "Zecke" more agile and prevents flipping to some degree. Since a few patches if a vehicle is sliding sideways and the speed is dropping below a certain limit (like 60 km/h or so) the wheels suddenly will have grip and the vehicle may flip.
     
  30. Lynnux

    Lynnux Junior Engineer

    Messages:
    881
    Ok, took a while. Here is the version with friction control depending on the sideways (sliding) speed:
    Code:
    /* Ackermann-Steering with friction control 0.52 */
    /* by plaYer2k, modified by Lynnux */
    
    /* Modify the following values as needed: */
    
    /* To move the focus point, middle value is forward(+)/backward(-) */
    Vector3D AnchorOffset = new Vector3D(0.0, -5.0, 0.0);
    /* Friction when going straight and of inside wheels when sliding */
    const float FrictionInside = 50.0f;
    /* Friction of outside wheels when sliding */
    const float FrictionOutside = 8.0f;
    /* Friction in case of braking when going straight and of inside wheels when sliding */
    const float FrictionBrakInside = 70.0f;
    /* Friction in case of braking of rear outside wheel(s) when sliding */
    const float FrictionBrakOutside = 15.0f;
    /* Above this sideways speed [m/s] the vehicle is considered sliding */
    const float SlideSpeedLimit = 5.0f;
    
    /* Don't modify lines below (or at own risk) */
    const bool debug = false;
    const double secondsElapsedInv = 60.0;
    
    WheelController wheelController;
    LinearSpeed speedInfo;
    
    static MyGridProgram GP;
    
    void Main(string arg)
    {
    	List<IMyTerminalBlock> wheels = new List<IMyTerminalBlock>();
    	GridTerminalSystem.GetBlocksOfType<IMyMotorSuspension>(wheels,
    		x =>(x.CubeGrid == Me.CubeGrid) &&(x.IsWorking));
    	if(wheelController == null || arg == "reset" || wheelController.WheelAdded(wheels.Count))
    	{
    		List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
    		GP = this;
    		GridTerminalSystem.GetBlocksOfType<IMyRemoteControl>(blocks,x =>(x.CubeGrid == Me.CubeGrid));
    
    		// Lets initialize an example wheel controller
    		wheelController = new WheelController(
    				blocks.Count == 0 ? Me : blocks[0],
    				wheels,
    				2f,
    				4f,
    				AnchorOffset
    			);
    	}
    	if( speedInfo == null) speedInfo = new LinearSpeed(wheelController.Anchor);
    	speedInfo.Update();
    	wheelController.Update(speedInfo.curForwardSpd,speedInfo.curLeftSpd);
    }
    
    public class LinearSpeed
    {
    	Vector3D speedVector;
    	float prevForwardSpd;
    	float prevLeftSpd;
    	float prevUpSpd;
    	Vector3D? prevVehiclePos;
    	Filter accFilter;
    	IMyTerminalBlock refBlock;
    	public float curForwardSpd;
    	public float curLeftSpd;
    	public float curUpSpd;
    	public bool reverse;
    	public float vehSpeed;
    	public float absForwardSpd;
    	public float signForwardSpd;
    
    	public float acceleration;
    	public float accLeft;
    	public float accUp;
    
    	class Filter {
    		float[] values;
    		int numValues;
    		int index;
    
    		public Filter(int num) {
    			index = 0;
    			if(num>0) numValues = num; else numValues = 1;
    			values = new float[numValues];
    			Array.Clear(values,index,numValues);
    		}
    
    		public void Add(float value) {
    			if(index >= numValues) index = 0;
    			values[index] = value;
    			index++;
    		}
    
    		public float Get() {
    			float sum = 0.0f;
    			for(int ix=0;ix<numValues;ix++) {
    				sum += values[ix];
    			}
    			return (float)(sum/numValues);
    		}
    	}
    
    	public LinearSpeed(IMyTerminalBlock refB)
    	{
    		prevForwardSpd = float.NaN;
    		prevLeftSpd = float.NaN;
    		prevUpSpd = float.NaN;
    		prevVehiclePos = null;
    		accFilter = new Filter(20);
    		refBlock = refB;
    	}
    
    	public void Update()
    	{
    		MatrixD refBase = MatrixD.Normalize(refBlock.WorldMatrix);
    		Vector3D? pos = refBlock.GetPosition();
    		/*** Linear speed and acceleration ***/
    		speedVector = new Vector3D(0,0,0);
    
    		if(pos.HasValue)
    		{
    			if(prevVehiclePos.HasValue) speedVector = (pos.Value - prevVehiclePos.Value) * secondsElapsedInv;
    			prevVehiclePos = pos.Value;
    		}
    		curForwardSpd = (float)Vector3D.Dot(speedVector,refBase.Forward);
    		curLeftSpd = (float)Vector3D.Dot(speedVector,refBase.Left);
    		curUpSpd = (float)Vector3D.Dot(speedVector,refBase.Up);
    		reverse = curForwardSpd < -0.1f;
    		vehSpeed = (float)speedVector.Length();
    		absForwardSpd = Math.Abs(curForwardSpd);
    		signForwardSpd = Math.Sign(curForwardSpd);
    
    		acceleration = 0.0f;
    		accLeft = 0.0f;
    		accUp = 0.0f;
    
    		if(prevForwardSpd != float.NaN) acceleration = (curForwardSpd - prevForwardSpd) * (float)secondsElapsedInv;
    		if(prevLeftSpd != float.NaN) accLeft = (curLeftSpd - prevLeftSpd) * (float)secondsElapsedInv;
    		if(prevUpSpd != float.NaN) accUp = (curUpSpd - prevUpSpd) * (float)secondsElapsedInv;
    		accFilter.Add(accUp);
    		prevForwardSpd = curForwardSpd;
    		prevLeftSpd = curLeftSpd;
    		prevUpSpd = curUpSpd;
    	}
    
    }
    
    public class WheelController
    {
    	static readonly Dictionary<string, Vector3D> WheelOffsetLookup = new Dictionary<string, Vector3D>
    		{
    			{ "Large", new Vector3D(0, 0, -3.1) },
    			{ "Small", new Vector3D(0, 0, -0.6) }
    		};
    
    	string EchoString;
    
    	List<Wheel> Wheels = new List<Wheel>();
    	int prevNumWheels;
    	IMyTerminalBlock anchor;
    	public Vector3D? center;
    	float SteerSpeedFactor;
    	float ReturnSpeedFactor;
    
    	/*
    		@anchor - The anchor block which gets used as reference for position and orientation.
    		@wheels - The list of wheels with which we have to work.
    		@steerSpeedFactor - This is a proportional factor for total steer speed over the max angle.
    		@returnSpeedFactor - This is a proportional factor for total return speed over the max angle.
    		@offset - The additional offset in forward and right direction.
    	*/
    	public WheelController(IMyTerminalBlock refBlock, List<IMyTerminalBlock> wheels, float steerSpeedFactor,
    		float steerReturnFactor, Vector3D offset)
    	{
    		Wheels.Clear();
    		anchor = refBlock;
    		MatrixD anchorMatrixInv = MatrixD.Invert(Anchor.WorldMatrix);
    		center = null;
    
    		if(debug) EchoString = "";
    		SteerSpeedFactor = steerSpeedFactor;
    		ReturnSpeedFactor = steerReturnFactor;
    
    		double FocusLeft = double.MaxValue;
    		double FocusRight = double.MinValue;
    
    		Vector3I anchorPosGrid = anchor.Position;
    
    		for(int i = 0; i < wheels.Count; i++)
    		{
    			// Check if the wheel actually is a suspension wheel.
    			IMyMotorSuspension wheel = wheels[i] as IMyMotorSuspension;
    			if(wheel == null)
    				continue;
    
    			// Get the transformed local matrix in respect to the new anchor with offset.
    			MatrixD wheelMatrix = wheel.WorldMatrix * anchorMatrixInv;
    
    			MatrixD wheelMatrixInv = wheelMatrix;
    			wheelMatrixInv.Translation = new Vector3D(0,0,0);
    			wheelMatrixInv = MatrixD.Invert(wheelMatrixInv);
    			Vector3D wheelOffset = WheelOffsetLookup.ContainsKey(wheel.BlockDefinition.SubtypeId) ?
    				WheelOffsetLookup[wheel.BlockDefinition.SubtypeId] : WheelOffsetLookup[wheel.CubeGrid.GridSizeEnum.ToString()];
    			wheelOffset = Vector3D.Transform(wheelOffset-offset, wheelMatrixInv);
    			wheelMatrix.Translation += wheelOffset;
    
    			if(i>0) center = center.Value + wheel.GetPosition();
    			else center = wheel.GetPosition();
    
    			float maxSteerAngle = wheel.GetMaximum<float>("MaxSteerAngle");
    			if(wheel.CustomName.Contains("MaxSteerAngle="))
    			{
    				string[] split = wheel.CustomName.Split(' ');
    				for(int j = 0; j < split.Length; j++)
    				{
    					if(!split[j].StartsWith("MaxSteerAngle="))
    						continue;
    					split[j] = split[j].Replace("MaxSteerAngle=","");
    					float angle = 0;
    					bool isDegree = split[j].EndsWith("°");
    					if(isDegree)
    						split[j] = split[j].Replace("°","");
    					if(!float.TryParse(split[j], out angle))
    						continue;
    					if(isDegree)
    						angle = VRageMath.MathHelper.ToRadians(angle);
    					if(angle < 0)
    						angle *= -1;
    					if(angle < maxSteerAngle)
    						maxSteerAngle = angle;
    				}
    			}
    			double Z = wheelMatrix.Translation.GetDim(2);
    			double tan = Math.Tan(maxSteerAngle);
    			double deltaX = Math.Abs(Z / tan);
    			double X = wheelMatrix.Translation.GetDim(0);
    			double CurrentFocusLeft = X - deltaX;
    			double CurrentFocusRight = X + deltaX;
    			//DEBUG
    			if(debug) EchoString += string.Format("> {0}\nX:{1:0.00} - Z:{2:0.00}", wheel.CustomName, wheelMatrix.Translation.GetDim(0),wheelMatrix.Translation.GetDim(2));
    				//GP.Echo(string.Format("> {0}\nX:{3} - Z\nangle: {4:0.000} - tan: {5:0.000}\n{1}:{2}",wheel.CustomName, CurrentFocusLeft, CurrentFocusRight,X,Z,maxSteerAngle,tan));
    			if(CurrentFocusLeft < FocusLeft)
    				FocusLeft = CurrentFocusLeft;
    			if(CurrentFocusRight > FocusRight)
    				FocusRight = CurrentFocusRight;
    
    			// Add the new wheel with parameters to the list of wheels.
    			Wheels.Add(new Wheel(
    					wheel,
    					wheelMatrix,
    					maxSteerAngle
    				));
    		}
    		if(debug) GP.Echo(EchoString);
    		prevNumWheels = Wheels.Count;
    		if(center.HasValue) center = center / prevNumWheels;
    		for(int i = 0; i < Wheels.Count; i++)
    		{
    			Wheels[i].SetFocus((float)FocusLeft, (float)FocusRight, SteerSpeedFactor, ReturnSpeedFactor);
    		}
    	}
    
    	public void Update(float forwSpd, float leftSpd)
    	{
    		Wheels.RemoveAll(x => !x.IsFunctional());
    		for(int i = 0; i < Wheels.Count; i++)
    		{
    			Wheels[i].Update(forwSpd, leftSpd);
    		}
    		prevNumWheels = Wheels.Count;
    	}
    
    	public bool WheelAdded(int currentNumWheels)
    	{
    		return(currentNumWheels>prevNumWheels);
    	}
    
    	public IMyTerminalBlock Anchor
    	{
    		get
    		{
    			return anchor;
    		}
    	}
    	public Vector3D? Center
    	{
    		get
    		{
    			return center;
    		}
    	}
    
    	public class Wheel
    	{
    		IMyMotorSuspension Block;
    		Vector3I blockPos;
    		IMyCubeGrid blockGrid;
    		bool leftSide;
    		bool front;
    		bool rear;
    		float AngleLeft;
    		float AngleRight;
    		float AngleMax;
    		MatrixD MatrixLocal;
    		MatrixD MatrixLocalInv;
    		float SteerSpeedLeft, SteerSpeedRight;
    		float ReturnSpeedLeft, ReturnSpeedRight;
    		float LastAngle;
    		float LastDirection;
    		float frictionInside;
    		float frictionOutside;
    		float frictionBrakInside;
    		float frictionBrakOutside;
    		float slideSpeedLimit;
    
    		public Wheel(IMyMotorSuspension block, MatrixD matrix, float angleMax)
    		{
    			Block = block;
    			blockPos = Block.Position;
    			blockGrid = Block.CubeGrid;
    			MatrixLocal = matrix;
    			AngleMax = angleMax;
    			frictionInside = FrictionInside;
    			frictionOutside = FrictionOutside;
    			frictionBrakInside = FrictionBrakInside;
    			frictionBrakOutside = FrictionBrakOutside;
    			slideSpeedLimit = SlideSpeedLimit;
    		}
    
    		public bool BlockExists()
    		{
    			return blockGrid.CubeExists(blockPos);
    		}
    
    		public bool IsFunctional()
    		{
    			return BlockExists() && Block.IsWorking;
    		}
    
    		public bool IsWorking()
    		{
    			return Block.IsWorking;
    		}
    
    		public void SetValueFloat(string prop, float value)
    		{
    			if(Block.GetValueFloat(prop)!=value) Block.SetValueFloat(prop, value);
    		}
    
    		public bool Update(float forwSpd, float leftSpd)
    		{
    			if((Block == null)||!Block.IsWorking)
    				return false;
    
    			bool outside = false;
    
    			float CurrentAngle = Block.SteerAngle;
    			float CurrentDirection = CurrentAngle - LastAngle;
    
    			if(Block.SteerAngle > 0.001f)
    			{
    				outside = !leftSide;
    				SetValueFloat("MaxSteerAngle", AngleLeft);
    				SetValueFloat("SteerSpeed", SteerSpeedLeft);
    				SetValueFloat("SteerReturnSpeed", ReturnSpeedLeft);
    			}
    			else if(Block.SteerAngle < -0.001f)
    			{
    				outside = leftSide;
    				SetValueFloat("MaxSteerAngle", AngleRight);
    				SetValueFloat("SteerSpeed", SteerSpeedRight);
    				SetValueFloat("SteerReturnSpeed", ReturnSpeedRight);
    			}
    
    			if(((leftSpd > slideSpeedLimit) && leftSide)||((leftSpd < -slideSpeedLimit)&& !leftSide))
    			{
    				if(Block.Brake && ((rear&&(forwSpd > 1.0f))||(front&&(forwSpd < -1.0f))))
    				{
    					SetValueFloat("Friction", frictionBrakOutside);
    				}
    				else
    				{
    					SetValueFloat("Friction", frictionOutside);
    				}
    			}
    			else
    			{
    				if(Block.Brake)
    				{
    					SetValueFloat("Friction", frictionBrakInside);
    				}
    				else
    				{
    					if(((front&&(forwSpd > 1.0f))||(rear&&(forwSpd < -1.0f)))&&outside)
    					{
    						SetValueFloat("Friction", frictionBrakOutside);
    					}
    					else
    					{
    						SetValueFloat("Friction", frictionInside);
    					}
    				}
    			}
    
    			// Final sets for the next run.
    			LastAngle = CurrentAngle;
    			LastDirection = CurrentDirection;
    			return true;
    		}
    
    		public void SetFocus(float focusLeft, float focusRight, float steerSpeedFactor, float returnSpeedFactor)
    		{
    			float X = (float)MatrixLocal.Translation.GetDim(0);
    			float Z = (float)MatrixLocal.Translation.GetDim(2);
    			AngleRight = (float)Math.Abs(Math.Atan(Z/((Z > 0 ? focusLeft : focusRight ) - X)));
    			AngleLeft = (float)Math.Abs(Math.Atan(Z/((Z > 0 ? focusRight : focusLeft ) - X)));
    			front = Z < 0.0f;
    			rear = Z > 0.0f;
    			SteerSpeedLeft = AngleLeft * steerSpeedFactor;
    			SteerSpeedRight = AngleRight * steerSpeedFactor;
    			leftSide = X < 0.0f;
    			ReturnSpeedLeft = AngleLeft * returnSpeedFactor;
    			ReturnSpeedRight = AngleRight * returnSpeedFactor;
    			if(debug)
    			{
    				Block.SetCustomName(Block.CustomName+(leftSide?" (left)":" (right)")+
    					(front?" (front)":"")+(rear?" (rear)":""));
    			}
    		}
    	}
    }
    I've added the LinearSpeed class and modified the Wheel class. With this script it should be possible to reduce/eliminate the tendency of flipping and to improve braking.

    Usage
    The script still uses the remote control or programmable block as reference (anchor) but also to determine the vehicle speed. Therefore it is mandatory now to place this block in the correct orientation.
    For the correct detection of the wheel geometry the anchor point (anchor position modified by anchor offset) has to be "inside" the wheel boundaries (front-rear + left-right).

    The parameters you may have to change are:
    Code:
    /* To move the focus point, middle value is forward(+)/backward(-) */
    Vector3D AnchorOffset = new Vector3D(0.0, 2.0, 0.0);
    /* Friction when going straight and of inside wheels when sliding */
    const float FrictionInside = 70.0f;
    /* Friction of outside wheels when sliding */
    const float FrictionOutside = 15.0f;
    /* Friction in case of braking when going straight and of inside wheels when sliding */
    const float FrictionBrakInside = 100.0f;
    /* Friction in case of braking of rear outside wheel(s) when sliding */
    const float FrictionBrakOutside = 20.0f;
    /* Above this sideways speed [m/s] the vehicle is considered sliding */
    const float SlideSpeedLimit = 5.0f;
    AnchorOffset is the position of the focus point as in the original script. A positive Y-value (the middle one) moves the focus to the front.

    FrictionInside [%] is the friction value of the wheels when driving straight or the inside wheels when sliding. It can be a high value but this may cause the vehicle to hop or to not drive smoothly. I'd say for fast/agile vehicles it should be high and for slow utility vehicles (trucks, mining vehicles, ...) a lower value may be better.

    FrictionOutside [%] is the friction value of the outside wheels when sliding. This value should be sufficiently low to prevent flipping of the vehicle under all circumstances. It's best to determine by setting all wheels to a low friction value and test the vehicle with this script deactivated. If the vehicle still flips when cornering/sliding then lower this value until it doesn't flip anymore. Safe values are usually 0% to 5%.

    FrictionBrakInside [%] is the friction value used for braking of the inside wheels when sliding or all wheels when going straight. You can set it to 100% and lower it when the vehicle tends to flip over the front wheels when braking.

    FrictionBrakOutside [%] is the friction value used for the outside wheel when braking and sliding and the wheel is the rear one (or the front one when reversing). It's also used for the outer front wheel in case of slow speed turns.
    It's a somewhat higher value than FrictionInside and has to be sufficiently low to prevent flipping. It's used to stabilise the vehicle a bit during braking when sliding because the vehicle will tend to fully slide sideways. If unsure what to set, just use the same value as for FrictionOutside.

    SlideSpeedLimit [m/s] is the decision value at which sideways speed the vehicle is considered sliding. Maybe begin with a low value 1.0f or 2.0f and increase it until the vehicle tends to flip when sliding. Don't forget to test it when braking while sliding. A high value will decrease the tendency of the vehicle to yaw when braking while going straight.


    I use this script (v0.5) in the Tigershark vehicle:
    https://steamcommunity.com/sharedfiles/filedetails/?id=602148929

    The vehicle isn't using artificial masses, spaceballs and thrusters to keep it on the ground. Instead the wings are creating the downforce. The friction values are optimised for planets (off-road). Grids require lower values.
    The wings (skirts) at the side are enabling tight turns at all speeds.
    Mods: Wings by Digi, Spectre cockpit by TumbleTV
     
    Last edited: Dec 14, 2016
    • Like Like x 1
Thread Status:
This last post in this thread was made more than 31 days old.