Search Unity

WheelCollider.GetWorldPos returns different Position, Rotation when given same Input

Discussion in 'Scripting' started by freiSMS, Jun 25, 2018.

  1. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    Hello,
    I am currently working on a Car game, where I want to use Machine Learning Skills. Because the Unity ML Agents Library needs Training Data I wanted to save the Movement Axis Values (horizontal, vertical and brake) to recover them in Training Sessions.
    The Car Movement is done by using Wheel Colliders where only the following values are set: motorTorque, steerAngle, brakeTorque. I wrote some log-Files and everything seems to work fine. The car is getting the same Axis and Brake Values at every Frame resulting in the same Angle and Torque values. Allthought this are the only relevant fields the position of the car is different (from the second frame). Like you can see in the code snippet the wheel position is calculated by calling the "GetWorldPos" Method.
    Code (CSharp):
    1.   Quaternion q;
    2.         Vector3 p;
    3.         wheel.GetWorldPose(out p, out q);
    4.  
    5.         // Assume that the only child of the wheelcollider is the wheel shape.
    6.         Transform shapeTransform = wheel.transform.GetChild(0);
    7.         shapeTransform.position = p;
    8.         shapeTransform.rotation = q;
    So this results in a few question:
    1. Do you have an idea why GetWorldPose() returns different results? (are there some random things like wind?)
    2. Is GetWorldPos only setting the visual parts of the wheel or does it update some wheel collider values?
    - Then I could save the returned p and q values in normal sessions and load them in Training (setting p and q directly instead of calling GetWorldPos). I would have to do it for every wheel seperated but maybe it could work.
    3. What are other good possibilties to store and recall car movement?

    I am happy about every hint =)
     
  2. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,089
    freiSMS likes this.
  3. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    Thanks for your fast answer =). The strange thing is that the recorded route yields to the same (different) training route in every training session. So there is some kind of deterministic behaviour isn't it?
    I simply can not find the difference between training and usual sessions. I think I checked every control parameter and documented it in this issue (https://github.com/plumskl0/Keelan_Neo/issues/33).

    I now also save and load the position the training car should have and added a sphere which is following this path. So in the scene I can compare the car trying to drive the route and the sphere which is on it. It s strange... they are both starting to drive forwards simultaneously but when the sphere does a hard move to left or right the training car is only doing this slightly (allthought the car has the samle angle and torque as the car which recorded the route in this moment).

    I wondered what happens if I also record the rotation of the car and instead of moving the wheel colliders I simply change the position and orientation of the car. I know that you should not move a gameObject like this because the simulation can get unrealistic, but in this case the recorded route allready found a realistic path right? What do you think?

    Edit: When I change the position it is like a teleport right? So I won't get the force I need to simulate on the car. Is something like "vector3.moveTowards()" the right way to do it?
     
    Last edited: Jun 26, 2018
  4. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232
    GetWorldPose
    returns values of the wheel (collider?) in world space.

    Assigning a child the position value from these values to
    position
    is the same as assigning
    Vector3.zero
    to
    localPosition
    . In general.

    Not knowing the relations of wheel colliders to wheels perhaps this is a helpful hint.
     
    freiSMS likes this.
  5. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    Hey @uani, thanks for your answer! But I am afraid that i did not understand it correctly. When I wrote that I "..now save the position of the car..." I mean the position of the car (the parent Object of the wheels)
    upload_2018-6-26_16-2-6.png . So I get a Dictionary which maps Framecounts to Positions. In a later session I want to replace the manual car control by looking for the position at the current frameCount.
    At what point can I use your method?
    Thanks =)
     
  6. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232
    I am referring to your original post. I wanted to give the hint about transform data such as position which is in world space and localPosition which is in local (object) space. See https://docs.unity3d.com/2018.2/Documentation/ScriptReference/Transform-localPosition.html. I sometimes forgot about this :| . The localPosition is the position relative to the parent while the position is the accumulated localPositions of itself an all parents to give the world position. Does this help?

    Edit: your sphere from your second post having a higher degree of rotation than the original data appears like it can be an issue of assigning world space data to local data thus getting that higher sum.

    Edit2: the transform data Position etc inside the Editor inspector is the local data, ie localPosition etc ! (This is confusing from Unitys part).
     
    Last edited: Jun 26, 2018
    freiSMS likes this.
  7. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    Ah ok =). Yeah this part was confusing at some points.
    But here I use the world space in every situation. The frustrating thing is, that I use the same method to control the car in training and usual sessions by asking GetWorldSpace to give me the correct coordinates for the wheels. The result depends on the wheelAngle, the motorTorque and the brakeTorque. I made sure that these values are the same in training and usual session =(
     
  8. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232
    So you have "(training) input data -> calculated output data -> applied to wheels". And you store the input data and feed it to the same calculation as before but receive different wheel orientations? If you haven't missed a mistake in your code I can only guess too this is part of the physics as GetWorldPose also takes suspension into account which might be unavailable at the time of replay. You might like to paste a longer excerpt from your code as a better basis for analysis and to help you :) Unfortunately I have no other idea to solve this :(
     
    freiSMS likes this.
  9. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    Normaly I have the horizontal and vertical Keyboard Axis which have values from [-1,1]. In fixedUpdate() the angle and torque are set by mulitpying this values with a max Angle of the wheels and a max torque (see Code below). So if moveHorizontal is 1 the saved maximum value for the wheel angle should be taken.
    Code (CSharp):
    1.          
    2.             angle = sharedData.maxWheelAngle * moveHorizontal;
    3.             torque = sharedData.maxTorque * moveVertical;
    4.  

    These values are later applied to the wheel collider:
    Code (CSharp):
    1.  
    2.         // Vordere Reifen lenken
    3.         getCollider(FRONT_LEFT).steerAngle = angle;
    4.         getCollider(FRONT_RIGHT).steerAngle = angle;
    5.  
    6.         // Bremsen mit allen Rädern
    7.         fullBrake(handBrake);
    8.  
    9.         // Antrieb auf alle Räder?
    10.         getCollider(FRONT_LEFT).motorTorque = torque;
    11.         getCollider(FRONT_RIGHT).motorTorque = torque;
    12.         getCollider(REAR_RIGHT).motorTorque = torque;
    13.         getCollider(REAR_LEFT).motorTorque = torque;
    14.  
    15.         // Räder bewegen
    16.         moveWheels(getCollider(FRONT_LEFT));
    17.         moveWheels(getCollider(FRONT_RIGHT));
    18.         moveWheels(getCollider(REAR_RIGHT));
    19.         moveWheels(getCollider(REAR_LEFT));
    20.  
    The moveWheel Function is the code from above. It calls the getWorldPos for every wheel and sets their position and rotation.

    Code (CSharp):
    1.  
    2.     private void moveWheels(WheelCollider wheel)
    3.     {
    4.         Quaternion q;
    5.         Vector3 p;
    6.         wheel.GetWorldPose(out p, out q);
    7.  
    8.         // Assume that the only child of the wheelcollider is the wheel shape.
    9.         Transform shapeTransform = wheel.transform.GetChild(0);
    10.         shapeTransform.position = p;
    11.         shapeTransform.rotation = q;
    12.     }
    13.  
    Every frame the values for moveHorizontal, moveVertical and handbrake are added to a StringBuilder with the actual FrameCount (and later saved to a file).

    When a Training Car starts it loads such a file to a dictionary where the keys are the frameCounts and the Value is a Vector3 with the three float values. So in my opinion the only thing a training car has to do is: look for the control values of the current frame and feed them to moveHorizontal, moveVertical and handbrake:
    Code (CSharp):
    1.  
    2.                     Vector3 controls = trainingsFahrroute[frameCountThisTrainingRoute];
    3.                     moveHorizontal = controls.x;
    4.                     moveVertical = controls.y;
    5.                     handBrake = controls.z;
    6.  
     
  10. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232
    fixedUpdate runs at a higher frame rate than Update by default thus you would have higher precision if you run everything above from fixedUpdate while replaying everything with "only" "screen refresh rate" of Update would yield less precision.

    Other than that using StringBuilder might incur float precision reconversion issues (you convert your input to string and convert it back, right?) Have you Debug.Log() your moveH/Z values to see whether they truly are the same? (Here no issue is listed but still test.)

    moveWheels() appears fine. Perhaps it is an intricacy of GetWorldPose I don't know :(
     
  11. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    I used FixedUpdate for the whole control because I wanted a smooth physik calculation.

    About the StringBuilder: I didn't write it correct - StringBuilder is only used for some debug files. The control Axis are added to a dictionary<float, Vector3>in FixedUpdate and finally written to a file with the function bellow when the player leaves the game. I checked the values in game with Debug.Log like you recommended against the values in the file. They have the same precision and values.

    Code (CSharp):
    1.    
    2. public void WriteRouteDictToFile(string filePath, Dictionary<int, Vector3> sourceDict)
    3. {
    4.     StreamWriter writer = new StreamWriter(filePath, true);
    5.  
    6.     foreach (KeyValuePair<int, Vector3> item in sourceDict)
    7.    {
    8.             String valueString = string.Format("({0}.{1}.{2})", item.Value.x, item.Value.y, item.Value.z);
    9.             String concat = string.Format("[{0}|{1}]", item.Key, valueString);
    10.             if (item.Key < trainingsFahrrouteSaved.Count)
    11.             {
    12.                 writer.Write(concat);
    13.                 writer.Write(";");
    14.             }
    15.             else if (item.Key == trainingsFahrrouteSaved.Count)
    16.             {
    17.                 writer.Write(concat);
    18.             }
    19.  
    20.      }
    21.  
    22.       writer.Close();
    23. }
     
  12. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232
    Are the values you add to valueString and save in the file identical to what you input? Debug.Log()
    item.Value.x
    in first line of foreach for example.

    Hint: you separate floats by ".". This probably results in issues when reading on english .NET because "." is the decimal separator there.

    Or better: Debug.Log() angle and torque (line 2 and 3 in first snippet in previous post of yours) when training and after you have fed the stored values. If they are the same they "must" result in the same output given your code, the exception being
    GetWorldPose
    which -- if the output is different -- is the only "indeterministic" function there and then the cause for which I have no answer. If the values are different, Debug.Log() backwards to see where the difference originates from and you have found your culprit :)
     
    freiSMS likes this.
  13. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    I am currently trying a few different approaches and it would be great to get your thoughts to it about possible drawbacks. The goal is to move the car along the stored positions but also give it the needed forces to achive this. Simply setting the transform.position can not achive this.
    So the idea is to store the velocity vector of the car at every frame. When given to a training car at the same start position this should yield to a training car following the wanted points (the wheel colliders now are dissabled in training mode). I have a kinematic sphere whose position is set to the wanted position every frame to compare how far the training car is away from the wanted route. It seems to work pretty good: There is some distance between the sphere and car - but it is not much. Because the wheel colliders are dissabled the cars rotation is not changed, leading to car at a good route but wrong orientation (for example it is driving with its back or side looking forwards some times). I need to fix that.

    Trial 1)
    The easiest way seemed to also store the orientation of the car an apply it to the training car in every frame. I thought that would only affect its orientation but now the car is following a different (bad) route. The real bad thing is, that small position changes can make the car to drive over higher terrain (for example small hills). From there the same velocity Vector yields to a flying car -.-^^. upload_2018-6-28_11-19-23.png (In the picture you can see one more strange thing: The sphere which is cinematic and does not have a collider went in this underneath the terrain. How is that possible? It should be not affected by something else than setting transform.position.) If I add one more control mechanism and set the position of the car by transform.position I get a solution which seems to be nice. I have a car which is following a route where it does not hit any hills or something and the car still gets a force to simulate driving.
    It is just that I know that the car would not take this route when only given this forces/velocity. My question is: Is this some kind of cheating which does not really simulate driving or is it ok because the goal of a car following a good trace with forces is achived. What do you think?

    Trial2)
    Instead of turning the wheel colliders of I also could let them do their work and noretheless set the transform.position. This way I would not manipulate the car controller as much as in the other case.
     
  14. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    I did:
    Code (CSharp):
    1.  
    2. Debug.LogErrorFormat("{0}| {1} {2} {3}; ", frameCountThisTrainingRoute, moveHorizontal, moveVertical, handBrake);
    3. trainingsFahrrouteSaved.Add(frameCountThisTrainingRoute, new Vector3(moveHorizontal, moveVertical, handBrake));
    4.  
    So debug shows the values before adding them to the dict. They are the same as the ones written to file =(,
    If the GetWorldPose causes the problem: do you think one of the above solutions is ok?
     
  15. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232
    Depends on what you want to achieve or how the requirements for what you want to achieve are. I think trial 1 and 2 are ok.

    But I wonder: if you replace the controller input by the stored controller input and the controller input is the only input to begin with there shouldn't be a difference. Is the dictionary key which is the frame count truly "correct" ?

    Code (CSharp):
    1. angle = sharedData.maxWheelAngle * moveHorizontal;
    2. torque = sharedData.maxTorque * moveVertical;
    moveH/Z are from Input.GetAxis() ? And you have something like

    Code (CSharp):
    1. Vector3 getInput()
    2. {
    3.     Vector3 inputReturnValue;    // .x == steering, .y == acceleration, ...
    4.     if(globalConfig.training)
    5.     {
    6.         inputReturnValue.x = Input.GetAxis("horizontal");
    7.         ...
    8.         tempGlobals.savedInput.Add(inputReturnValue);    // savedInput can be a List<Vector3>
    9.     }
    10.     else
    11.     {
    12.         inputReturnValue = tempGlobals.savedInput[tempGlobals.inputCounter++];    // inputCounter is only an index of an "array": playback values identically to the order they were stored in
    13.     }
    14.     return inputReturnValue;
    15. }
    ?
     
  16. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232
    btw.: rather than checking for globalConfig.training in every getInput() call, you would use an Action or Func delegate which you assign the proper function on change to globalConfig.training or on initialisation (use script execution order to have the initialisations' globalConfig evaluation take place before your car logic).
     
  17. freiSMS

    freiSMS

    Joined:
    Mar 21, 2018
    Posts:
    11
    The frame count has been the unity framecount in the beginning, which did not work well because the time (in frames) till the Start Methods are ready and the car starts driving are different across session. Therefore the framecount ist simply an integer which gets incremented every FixedUpdate Call. Because I want to set a new Keystroke every FixedUpdate call I think that should work.

    I have a carController Script which looks in a global singleton for the activated control method. This are "Wiimote", "Training", "Keyboard". It is set in a unity menu before entering the game. Every control method has a code block in the carControler Script (in fixedUpdate) and sets the moveH/moveV if this method is activated.
    If training is activated it looks in the dict at the current frameCount:
    Code (CSharp):
    1.  
    2.                     Vector3 controls = trainingsFahrroute[frameCountThisTrainingRoute];
    3.                     moveHorizontal = controls.x;
    4.                     moveVertical = controls.y;
    5.                     handBrake = controls.z;
    6.  
     
  18. uani

    uani

    Joined:
    Sep 6, 2013
    Posts:
    232