Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

[SOLVED] Why is Transform.lossyScale readonly?

Discussion in 'Scripting' started by ArachnidAnimal, Oct 26, 2015.

  1. ArachnidAnimal

    ArachnidAnimal

    Joined:
    Mar 3, 2015
    Posts:
    1,727
    Sometimes you want to adjust the lossy scale of a game object. Setting the transform's localScale equal to the desired lossyScale only works for certain situations.

    If works for this example:
    GameObject A. Local scale = 1,1,1
    ------>GameObject B. Local scale = 1,1,1

    But you may have this:
    GameObject A. Local scale = 2,2,2
    ------>GameObect B. Local scale = 1,1,1

    If you want GameObject B to be lossy scale 2,2,2, you cannot set the GameObject B to have a local scale of 2,2,2, because then it would have a lossy scale of 4,4,4.

    So how can you get around this without reading the entire hierarchy upwards?

    It would be nice to have an API function to set a lossy scale, which in turn, automatically sets the local scale such that the gameOjbect ends up with the lossy scale you requested.
     
    Last edited: Oct 26, 2015
  2. Manny Calavera

    Manny Calavera

    Joined:
    Oct 19, 2011
    Posts:
    205
    If you want to set the global scale of an object that is parented you can unparent it, set the scale and then parent back.

    In your example:

    Code (CSharp):
    1. gameObjectB.transform.parent = null;
    2. gameObjectB.transform.localScale = new Vector3(2f, 2f, 2f);
    3. gameObjectB.transform.parent = gameObjectA.transform;
     
    MUGIK, mhmtbtn and ArachnidAnimal like this.
  3. ArachnidAnimal

    ArachnidAnimal

    Joined:
    Mar 3, 2015
    Posts:
    1,727
    Ok, thanks for this.
    It's surprising the Unity API doesn't already provide a way to do this.
     
  4. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    The reasoning behind this is probably that it does not represent anything useful as soon as you apply some rotations and non uniform scale. Child objects deform in a way that gives a scale value in world space little meaning. Positions and rotation can be pretty accurately represented, but scale not so much.

    edit: It's hard to type on a phone that doesn't let you see what you type while typing and doesn't show you the caret until after you have typed.
     
    Last edited: Oct 27, 2015
    Alien1997 and ArachnidAnimal like this.
  5. ArachnidAnimal

    ArachnidAnimal

    Joined:
    Mar 3, 2015
    Posts:
    1,727
    That might be true for most scenarios, but in my case I'm taking a game object and bringing it a certain distance in front of the camera, and I know exactly what the global scale needs to be, it's hard-coded in the software. The parenting in the hierarchy can change during gameplay, so I can't hard-code a local scale. The above code works fine for setting the global scale.
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Note, ThermalFusion is actually right.

    The problem with setting some global scale when its the child of something is that... what axes are the scales representing? Is it the global x, or the local x? The lossyScale that spits out from unity is the scale as it would be on each 'local' axis. But a 'globalScale' isn't very intuitive at conveying that idea. If you flip something 90 degrees around the z, and than globally scale it in the y by 2, would you expect it to grow up/down or left/right. scaling up/down would be 'global' in description, but lossyScale represents the 'left/right'. Because 'lossyScale' represents the local scale with all its parents scales applied. Not its scale in global space!

    It's all a bit wishy washy.

    It's why Unity calls it lossyScale. You lose information. So to set it back you have to invent information.


    BUT, yes, I agree there can be uses for this. Such as the situation you describe.

    Note though, changing of the transform parent is actually an expensive call. You're restructuring the hierarchy.

    You can do this arithmetically instead.

    Here you can see I do it in my 'Trans' struct:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/Geom/Trans.cs
    Code (csharp):
    1.  
    2.         public void SetToGlobal(Transform trans, bool bSetScale)
    3.         {
    4.             if (bSetScale)
    5.             {
    6.                 trans.position = Position;
    7.                 trans.rotation = Rotation;
    8.                 trans.localScale = Vector3.one;
    9.                 //this simulates what it would be like to set lossyScale considering the way unity treats it
    10.                 var m = trans.worldToLocalMatrix;
    11.                 m.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
    12.                 trans.localScale = m.MultiplyPoint(Scale);
    13.             }
    14.             else
    15.             {
    16.                 trans.position = Position;
    17.                 trans.rotation = Rotation;
    18.             }
    19.         }
    20.  
    21.         public void SetToGlobal(Transform trans, bool bSetScale, bool bSetScaleOnGlobalAxes)
    22.         {
    23.             if (bSetScale)
    24.             {
    25.                 trans.position = Position;
    26.                 trans.rotation = Rotation;
    27.                 trans.localScale = Vector3.one;
    28.                 var m = trans.worldToLocalMatrix;
    29.                 if(bSetScaleOnGlobalAxes)
    30.                 {
    31.                     m.SetColumn(0, new Vector4(m.GetColumn(0).magnitude, 0f));
    32.                     m.SetColumn(1, new Vector4(0f, m.GetColumn(1).magnitude));
    33.                     m.SetColumn(2, new Vector4(0f, 0f, m.GetColumn(2).magnitude));
    34.                 }
    35.                 m.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
    36.                 trans.localScale = m.MultiplyPoint(Scale);
    37.             }
    38.             else
    39.             {
    40.                 trans.position = Position;
    41.                 trans.rotation = Rotation;
    42.             }
    43.         }
    44.  
    See though that I have 2 parameters controlling it. This conveys the ambiguity of setting a global scale.

    1 parameter is for if we'll set it at all (usually you wouldn't, cause its not directly supported, and the idea is rather odd).
    Another parameter defines if we are going to set it on the global axes or the local axes.

    The local axes version is the cheapest (no magnitude calls). And since you really shouldn't be setting global scales unless you all of its parents have NO rotation... it works as it assumes no rotation.

    So go with that mathematical algorithm.
     
    Last edited: Oct 28, 2015
    LuLusg, ethantgroat, Kiwasi and 2 others like this.
  7. ArachnidAnimal

    ArachnidAnimal

    Joined:
    Mar 3, 2015
    Posts:
    1,727
    Knowing all of the parent rotations will be (0,0,0) I created the following function.
    The function determines the global-to-local scale factor

    Code (csharp):
    1.  
    2. public static Vector3 GetGlobalToLocalScaleFactor(Transform t)
    3.    {
    4.      Vector3 factor = Vector3.one;
    5.  
    6.      while(true)
    7.      {
    8.        Transform tParent = t.parent;
    9.  
    10.        if (tParent != null)
    11.        {
    12.          factor.x *= tParent.localScale.x;
    13.          factor.y *= tParent.localScale.y;
    14.          factor.z *= tParent.localScale.z;
    15.  
    16.          t = tParent;
    17.        }
    18.        else
    19.        {
    20.          return factor;
    21.        }
    22.      }
    23.    }
    24.  
    The code is used with:
    Code (csharp):
    1.  
    2. Vector3 desiredGlobalScale = new Vector3(0.5f, 1.0f, 10.0f);//What you want the global scale to be
    3. Vector3 scaleFactor = U.GetGlobalToLocalScaleFactor(this.gameObject.transform); //Determine the factor
    4.  
    5.     //Determine what the new scale local scale should be
    6.      Vector3 newLocalScale = new Vector3
    7.        (desiredGlobalScale.x / scaleFactor.x,
    8.         desiredGlobalScale.y / scaleFactor.y,
    9.         desiredGlobalScale.z / scaleFactor.z);
    10.  
    11.      this.gameObject.transform.localScale = newLocalScale; //Set the new local scale
    12.     //Now the gameObject has the requested global scale
    13.  
    I executed this 100,000 times and it took: 0.06 seconds.

    The other method above (with setting the transform to null, then re-assigning the parent) took 0.42 seconds, which is 7 times longer than my code.

    I haven't yet tried your code.
    99% of the time when I want to set a scale, it is done by setting the local scale. Very rare that I care about the global scale
     
    Last edited: Oct 28, 2015
  8. ArachnidAnimal

    ArachnidAnimal

    Joined:
    Mar 3, 2015
    Posts:
    1,727
    I tried your code. I could not get it to work.
    This is what I have:
    Code (csharp):
    1.  
    2. public void SetToGlobal(Transform trans, Vector3 desiredScale)
    3.    {  
    4.      var m = trans.worldToLocalMatrix;
    5.      m.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
    6.      trans.localScale = m.MultiplyPoint(desiredScale);  
    7.    }
    8.    
    9.    public void SetToGlobal(Transform trans, bool bSetScaleOnGlobalAxes, Vector3 desiredScale)
    10.    {
    11.      var m = trans.worldToLocalMatrix;
    12.      if(bSetScaleOnGlobalAxes)
    13.      {
    14.        m.SetColumn(0, new Vector4(m.GetColumn(0).magnitude, 0f));
    15.        m.SetColumn(1, new Vector4(0f, m.GetColumn(1).magnitude));
    16.        m.SetColumn(2, new Vector4(0f, 0f, m.GetColumn(2).magnitude));
    17.      }
    18.      m.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
    19.      trans.localScale = m.MultiplyPoint(desiredScale);
    20.    }
    21.  
    22. void test()
    23. {
    24. Vector3 desiredGlobalScale = new Vector3(3.0f, 3.0f, 3.0f);
    25.  
    26.      Debug.Log ("1: Global Scale Before: "+ this.gameObject.transform.lossyScale.ToString());
    27.      SetToGlobal(this.gameObject.transform, desiredGlobalScale);
    28.      Debug.Log ("2: Global Scale After: "+ this.gameObject.transform.lossyScale.ToString());
    29. }
    30.  
    The console outputs for both functions are:
    1: Global Scale Before: (20.0, 54.0, 112.0)
    2: Global Scale After: (1.5, 1.0, 0.8)


    Using both functions, the result global scale is not the desiredGlobalScale. Maybe I'm doing something wrong.

    scale.png

    Here is my hierarchy:
    ObjectA: local scale = 2,3,4
    ObjectB: local scale = 5,6,7
    ObjectC: local scale = 2,3,4
    GLOBAL SCALE of C: 20, 54, 112

    (All positions and rotations are 0)
    The code executes on ObectC transform

    The global scale of the object before executing the code is a multiplication of local scales up the hierarchy.
    I'm not sure how it arrived at the final global scale of (1.5, 1.0, 0.8) after executing your code.
    Maybe i am doing something wrong?

    j
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    You know, I forgot to include that use case of the object having a none one scale when using this.

    But that's easy enough to deal with. Just set its scale to 'one' before getting its matrix.

    Code (csharp):
    1.  
    2.         public void SetToGlobal(Transform trans, bool bSetScale)
    3.         {
    4.             if (bSetScale)
    5.             {
    6.                 trans.position = Position;
    7.                 trans.rotation = Rotation;
    8.                 trans.localScale = Vector3.one;
    9.                 var m = trans.worldToLocalMatrix;
    10.                 m.SetColumn(3, new Vector4(0f, 0f, 0f, 1f));
    11.                 trans.localScale = m.MultiplyPoint(Scale);
    12.             }
    13.             else
    14.             {
    15.                 trans.position = Position;
    16.                 trans.rotation = Rotation;
    17.             }
    18.         }
    19.  
     
    kilik128 and ArachnidAnimal like this.
  10. ArachnidAnimal

    ArachnidAnimal

    Joined:
    Mar 3, 2015
    Posts:
    1,727
    Ok, I tried it again after adding trans.localScale = Vector3.one;, it works now.
    It is 5 times faster than my solution, which means it 35x faster than using the first code solution posted here.
    I have to read up more on what exactly a Matrix4x4 is and how to use them. Up until now I've never needed to use them.
    This is interesting.
    Thanks.
     
    Last edited: Oct 28, 2015
    kilik128 likes this.
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Matrices are used to transformations in linear algebra.

    It's a set of rows and columns in a grid that defines the alterations to some value to transform it between 2 different spaces.

    In the case of 3d, you get a 4x4 matrix (4 rows by 4 columns) which can represent scale, position, rotation, and skew.

    Column 1 is the x axis
    Column 2 is the y axis
    Column 3 is the z axis
    Column 4 is the translation value

    The direction of the various axes is the rotation. If those axes are not adjacent to one another then there is skew. And the magnitudes of the axes are the scale.

    And the arithmetic operations of them allow you to conjoin matrices. You can multiply 1 matrix to another to create a new matrix that is the both combined. So if you take the matrices of every parent and multiply them together, you get the total transformation all the way down to the child. And you can invert it to get the opposite, like division.

    And this is all localToWorldMatrix and worldToLocalMatrix is. It's the combined matrix of all parents with its self.
     
    Last edited: Oct 28, 2015
  12. MCLiving88

    MCLiving88

    Joined:
    Aug 26, 2020
    Posts:
    7
    For any searching who land here. A simpler solution is to get the global scale up to the parent using the lossy scale call. Then scale your desired scale by that. This is basically what ArachnidAnimal was doing but just 3 or so lines.
    Code (CSharp):
    1.  
    2. // Get lossy scale up to parent.
    3. Vector3 scaleFactor = transform.parent.lossyScale;
    4. //Determine what the new scale local scale should be
    5. Vector3 newLocalScale = new Vector3 (
    6. _minimumLossyScale.x / scaleFactor.x,
    7. _minimumLossyScale.y / scaleFactor.y,
    8.  _minimumLossyScale.z / scaleFactor.z
    9. );
    10.  transform.localScale = newLocalScale;
    11.  
     
    qwert024 likes this.