Search Unity

Lerp/MoveToward local position: threads are generally mistaken. Some ideas on it and call for help

Discussion in 'Scripting' started by andyastro, Sep 21, 2015.

  1. andyastro

    andyastro

    Joined:
    Sep 17, 2015
    Posts:
    16
    It is fairly surprising how many incomplete or rather wrong replies I have found online in this forum, as well as in the Q.A. section and in Gamedev, when looking for a way to implement Lerping, Slerping or MovingToward in relation to a parent, i.e. in local position. I will explain the problem and later ask for you guys to say what do you think of the only solution I could think of (improvements are needed).

    Bear with me. I have the following setting: Cube B, the character, is a child of Cube A, the floor. I have to make B move around over the top face of A, using only Lerp, Slerp or MoveToward (other options are excluded here, for the purpose of this question). And it has to move only in terms of local position, i.e. in reference to the parent A.

    As I said, many people have asked this before (in the end I link to some of these debates), and replies are usually that one should merely go for it, with the usual line of code to give the final command:

    Code (CSharp):
    1. CubeB.transform.localPosition = Vector3.Lerp(CubeB.transform.localPosition , newlocalposition, pct);
    However, as we know, in programming, the fact that a resulting number was yielded from a formula does not mean that the desired thing was achieved. That approach to local Lerp/Slerp/MoveToward simply does not work properly for most game purposes. The reason is the following: no matter how big are the X and Z (let's forget the Y for the sake of simplicity) dimensions of my floor, i.e. the Cube A, the local space of its child Cube B is always 0<X<1 and 0<Z<1. Which, of course, distorts the results when one uses Lerp/Slerp/MoveToward on local coords, since B takes the same amount of time to move over the width or the depth of A, no matter the size of the dimensions of A in global scale! Hence, the movement is accelerated in different magnitudes depending on the axis (even when using MoveToward), because in local scale, the parent dimensions is always 1,y,1 to the children.

    For a concrete example, suppose B is located at the lower-left corner of the top face of Cube A and then moves to the lower-right corner and then form there to the up-right corner. That will always mean, in local coords, that Cube B started at X=-.5, Z= -.5 then moved to X= +.5, Z= -.5 and then later from there to X= +.5, X= +.5. No matter the dimensions of Cube A, the parent, in global scale. No matter for instance if Cube A is X=10, z= 200 in global terms, etc. Therefore, the visual effect caused when Learping/Slearping/MovingTowards in local coord is that Cube B would move in much greater velocity when going trough z than when going trough x.

    So, all this to ask, beg, implore: does any charitable soul know how can one properly use Lerp/Slerp/MoveToward related to a parent, i.e. in the local space, in local terms? I mean, in a way that, in the end, it does not take the same amount of time for Cube B in my example to move different distances? So far I came up with the following, but I am not sure this is the best/most efficient/elegant way of achieving the desired result (still, it might be of some help for lost souls like mine):


    Code (CSharp):
    1.   IEnumerator MovingNPC_go(Vector3 newlocalposition)
    2.     {
    3.         float cubeB_speed= 1.5F;
    4.         float cubeA_width = cubeA.GetComponent<Renderer>().bounds.size.x;
    5.         float cubeA_depth = cubeA.GetComponent<Renderer>().bounds.size.z;
    6.  
    7.         while (cubeB_localPos != newlocalposition)
    8.         {
    9.             cubeB_localPos = cubeB.transform.localPosition;
    10.             float global_x = Mathf.Lerp(cubeB_localPos.x*cubeA_width,newlocalposition.x*cubeA_width,Time.deltaTime*cubeB_speed);
    11.             float global_z = Mathf.Lerp(cubeB_localPos.z*cubeA_depth,newlocalposition.z*cubeA_depth,Time.deltaTime*cubeB_speed);
    12.  
    13.             npc.transform.localPosition = new Vector3(global_x/cubeA_width,cubeB_localPos.y ,global_z/cubeA_depth);
    14.             yield return null;
    15.         }
    16.  
    17.     }
    If anyone could give me a hand here, do not be shy. I would kindly appreciate your kind attention.


    PS: examples of former debates on this issue, which either do not address the issue or give fairly mistaken solutions:

    http://answers.unity3d.com/questions/447874/local-lerp-to-origin.html
    http://answers.unity3d.com/questions/218852/how-can-i-linear-interpolate-lerp-in-local-space.html
    http://forum.unity3d.com/threads/local-lerp-possible.218763/
    http://answers.unity3d.com/questions/464277/how-can-i-vector3-slerp-or-lert-locally.html
    http://answers.unity3d.com/questions/658082/vector3lerp-doesnt-work-in-local-position.html
    http://answers.unity3d.com/questions/894966/how-to-lerp-a-position-along-one-axis-only.html
     
  2. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    you should probably look up how to use lerp in general first... you've fallen into the usual trap of "just put time.delta time" in the t parameter and expect it to move consistently.

    Lerp is purely a mathematical function to work out a mid point. If you just use time.delta time you will be getting a diminishing distance each frame as you move closer to the end point (roughly same % of a smaller number frame after frame).
     
  3. andyastro

    andyastro

    Joined:
    Sep 17, 2015
    Posts:
    16
    Oh, sorry, my mistake. It was a typo, actually. It was supposed to be MoveTowards in that example.
     
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Wait what? The line of code you've given will work.

    I can only assume your problem is you have done some scaling on the parent object that's interfering.

    But your description of how local space coordinates work is just messed up.
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    Most of your post hinges on this, and it's simply wrong - if by local space you mean what Unity calls localPosition. The local position is an absolute offset from the parent position. The scale (and rotation) of the parent object does not affect what the local position does.

    A lot of people are linking this blog post when asked about Lerp, as it's both contains a good explanation of exactly what LinearintERPolation is, and gives some good alternatives if it's not quite correct for the situation at hand.
     
  6. andyastro

    andyastro

    Joined:
    Sep 17, 2015
    Posts:
    16
    Hi, I thank you both for replying, specially @BoredMormon for actually taking a look at the code to give his evaluation. I don't think that code of mine is optimal, and I wanted to ask how people deal with the problem since pretty much all posts I've found neglect the problem I have described. But indeed I bet that code of mine is an imporvement over just doing Lerp(original.localPosition, new.localPosition), as people usually suggest here.

    Now, @Baste, I will have to agree with you only partially. First of all, "most" of my posts is a little exaggeration, since this is probably the second of mine dealing with local position, certainly the first on this matter. But you are correct, the way I have worded it in that quote is indeed wrong. The local space (the possible values of localPosition) of child Cube B is of course infinite. Sorry for that and thanks for making me aware. Let's try again. If child B is positioned at any of the vertices of the bounding box of parent A, the X and Z axis of the local position of B will lay between -.5 and +.5. No matter, indeed, as I also have said, the global scale of parent A. But what you seem to have missed is that it is precisely the problem at hand for someone wanting to Lerp/Slerp/MovaToward at the local positions.

    Movement will have distorted velocity, for a games purpose. Just try yourself. Create two objects, one as child and one as parent, put the child at each vertice of the bounding box of the parent and retrieve the localPosition of the child. Then make the child MoveToward, from one vertice of the bounding box of the parent, to the next. You will see that the movement takes the same amount of time, no matter if one of the sizes of that bounding box is much greater than the other in the global scale, since at the local, by default the width and depth scales are 1.
     
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    You are right, the parent scale affects what the local position means. Look at me being sure of stuff and being all wrong, sorry about that!

    Okay, so the obvious solutions are:

    - Don't have your object be a child object. Parent objects that are non-uniformly scaled can cause a bunch of confusion. If you need a parent object for scene cleanliness, don't have the ground be that parent object, but create an empty folder object.

    - Scale your movement by the parent's scale. If your parent object is scaled to X, move your child in local space at the speed (speed * deltaTime * (1 / X)) should give exactly the same speed regardless of the parent's scale.
     
    Kiwasi likes this.
  8. andyastro

    andyastro

    Joined:
    Sep 17, 2015
    Posts:
    16
    No problem, Baste! I know these things with local vs. global are tricky and my intention was precisely to open up some debate, once I noted that few, virtually no replies, in the forums or QA dealt with the problem we are now talking about.

    So, your new suggestion looks good to me. But actually, I think it is just another way to do the same thing I proposed in my initial code. In any case, when looking to what you wrote and also to another question someone recently posted, I got ideas for a couple of other tweaking:

    1) one should always either put the parent within an Empty GameObject (in my example, Cube A), or unchild the main object (Cube B) and make it and the former parent (Cube A) as children-only of an Empty GameObject. The reason is, as many have already told in the forums, to avoid the problem of non-uniform scale of parent-child. It means to put every children's local scale at the beautiful and uniform 1,1,1 scale;

    2) when Lerping/Slerping/MovingToward at the LocalScale, a good idea might be to also divide the speed/rate of the desired move method by that uniform scale of the parent. For instance:

    Code (CSharp):
    1. speed /= Parent.localScale.x
     
    Last edited: Sep 22, 2015