- Home /
Version of Transform.TransformPoint which is unaffected by scale?
Is there a scale-independent version of Transform.TransformPoint?
I am trying to convert a position to a localized position relative to an object, and then round that position. The problem is that with the scale dependent Transform.TransformPoint method, the position is rounded to fractions of the object's scale instead of to an absolute distance away from the center of the object. Any help would be appreciated, thanks.
My current code:
// makes the point's position local to the gameobject that was hit by the raycast
tTemp.position = hit.collider.gameObject.transform.InverseTransformPoint(hit.point);
// rounds it to the apropriate amount
tTemp.position = new Vector3
(
Mathf.Round(tTemp.position.x * gridSize)/gridSize,
Mathf.Round(tTemp.position.y * gridSize)/gridSize,
Mathf.Round(tTemp.position.z * gridSize)/gridSize
);
// translates the position back into world space
tTemp.position = hit.collider.gameObject.transform.TransformPoint(tTemp.position);
have you tried multiplying the position by the scale? You would do this before you convert from local to global space.
tTemp.position = new Vector3(tTemp.position.x * hit.transform.localScale.x, tTemp.position.y * hit.transform.localScale.y, tTemp.position.z * hit.transform.localScale.z);
I've thought about that, but it only seems to make the translated position further away from the comparison object.
I think the problem is the scale itself. If you notice when you have a scaled object and you place a nonscaled object as a child, that childs displayed scale changes relative to its parent. Its best practice to have your assets scale be 1,1,1. If you need to scale art assets thats usually fine as its just a visual display, but if youre trying to make objects children of scaled objects and do what youre trying to do it becomes a headache. Have you tried placing stuff on a non-scaled object?
Is tTemp a child of hit.collider.transform?
Judging by the way you use both TransformPoint and InverseTransformPoint (as if they were the same thing) one can come to conclusion that you confuse local and world positions here.
tTemp.position - is a World Space position
hit.point - is a World Space position too
InverseTransformPoint() - returns Local Space position (and expects World Space position as argument)
TransformPoint() - returns World Space position (and expects Local Space position as argument)
transform.position - assign here ONLY World Space positions (and returns World Space position)
transform.localPosition - assign here ONLY Local Space position (and returns Local Space position)
So then, this is invalid:
// makes the point's position local to the gameobject that was hit by the raycast
tTemp.position = hit.collider.gameObject.transform.InverseTransformPoint(hit.point);
//comment: No. It does not. Assigning Local Space position to inheritly World Space transform.position does not make it local
syntactically fixed version:
//World Space solution:
tTemp.position = hit.point;
//OR
//Local Space solution:
rTemp.parent = hit.collider.transform;
rTemp.localPosition = hit.collider.transform.InverseTransformPoint( hit.point );
Another troubling line:
// translates the position back into world space
tTemp.position = hit.collider.gameObject.transform.TransformPoint(tTemp.position);
//comment: No it does not. This line contradicts itself...
//TransformPoint expects Local Space position as argument not World Space one
//(which tTemp.position definitely is)
//So... you end up here incorrectly converting tTemp.position (World Space) to hit.collider.transform's Local Position (which will result in garbage position) and then assing it to tTemp.position again... not good
this question is a spinoff of : http://answers.unity3d.com/questions/1237065/rounding-a-position-against-the-surface-of-an-obje.html#comment-1237479
I think your code will work when you no longer confuse World and Local spaces. So really make sure you understand the distinction first
EDIT: I mistook you sir for an author of this question : ) sorry about that!
Answer by villevli · Sep 02, 2016 at 12:04 PM
I made a version of Transform.TransformPoint which is unaffected by scale as the title of this question suggested. I don't know if it helps with the problem? This is an extension class that adds unscaled versions of the TransformPoint methods to the Transform class.
using UnityEngine;
using System.Collections;
public static class TransformExtensions
{
public static Vector3 TransformPointUnscaled(this Transform transform, Vector3 position)
{
var localToWorldMatrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
return localToWorldMatrix.MultiplyPoint3x4(position);
}
public static Vector3 InverseTransformPointUnscaled(this Transform transform, Vector3 position)
{
var worldToLocalMatrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
return worldToLocalMatrix.MultiplyPoint3x4(position);
}
}
This actually worked even better than my proposed solution above. Thanks a bunch!
Note that those would be more efficient:
public static class TransformExtensions
{
public static Vector3 TransformPointUnscaled(this Transform transform, Vector3 position)
{
return transform.position + transform.rotation * position;
}
public static Vector3 InverseTransformPointUnscaled(this Transform transform, Vector3 position)
{
position -= transform.position;
return Quaternion.Inverse(transform.rotation) * position;
}
}
Creating a matrix out of the position and rotation and then perfor$$anonymous$$g a matrix vector multiplication every time is quite a lot of overhead. Let alone calculating the inverse of a 4x4 matrix. The inverse of a quaternion is much simpler to calculate.
Creating a matrix makes only sense if you store it and use it multiple times on many points / vectors
Though that said there should never be the need for this extension. A Transform component represents a new seperate coordinate system. If you don't want a scale, you shouldn't scale the transform in the first place. If a model should be scaled, make the model a child of the transform that represents your coordinate system and just scale that child and keep the parent untouched.
Easy to say, but sometime you just want to position a point relative to a transform using a local offset, without any scaling co$$anonymous$$g into play.
Answer by b1gry4n · Sep 02, 2016 at 11:25 AM
I have never done anything like this so I am unsure. For me its easier to wrap my head around when I start separating things. I think the problem might be (as andrew points out) you are referencing the transforms.position in the last bit. I dont know your setup, but I am guessing the object is not a child of anything. You dont want to plug in "transform.position" into the TransformPoint part as you are getting the transforms world coordinates. You want the local coordinates.
for example:
if the hit point is (45.82, 21.02, 16.57), translating that to the objects local coordinates might make the local coordinates be (2.5,4.2,-3.92). Based off your grid size, rounding that might give you something like (2,4,-4). You want to plug that last bit into the TransformPoint part. Right now you are plugging in its world coordinates. So in your code, youve moved tTemp to the new local position (2,4,-4), but in world space. Now tTemp sits at (2,4,-4) in world coordinates when it should be closer to the hit.point values. From there you are plugging in that world position as a local value to the object you are referencing, which could be something wild like (-50, -5, 73). hopefully that makes sense.
// The hit point is translated to local space from world space
Vector3 localSpace = hit.collider.gameObject.transform.InverseTransformPoint(hit.point);
// the local position is rounded to the grid
Vector3 localSnappedGrid = new Vector3
(
Mathf.Round(localSpace.x* gridSize)/gridSize,
Mathf.Round(localSpace.y* gridSize)/gridSize,
Mathf.Round(localSpace.z* gridSize)/gridSize
);
// the local position is translated to world space and the object is placed
tTemp.position = hit.collider.gameObject.transform.TransformPoint(localSnappedGrid);
PS: I had it like this in the other question you asked, you changed it and put transform.position :p
I think you sir solved @beau101023 's problem here
That seems to do the same thing as my script though. You see, I was merely using tTemp.position as an intermediary storage variable just as you are doing with localSpace and localSnappedGrid. $$anonymous$$y problem appears to have been fixed by multiplying gridSize by Transform.lossyScale.
I think my problem was that in local space, the position of the object away from the center of the 'parent' is measured in parent-scales. So if the parent's scale was 9 on one axis, having the 'child' be 9 units away and converting to local space would return a 1 on that axis.
Thank you for all your help, @b1gry4n , @andrew-lukasik . It is very much appreciated. :)
Edit: I'm not sure about the etiquette on these things, so I'll just leave this question unanswered rather than marking my own answer correct.
Your answer
Follow this Question
Related Questions
Image with material is not render 0 Answers
How to follow the player without moving camera? 0 Answers
How to aply force to parent gameObject using childs local space coordinates? 0 Answers
Class member Vector3 based on local transform 1 Answer
Convert LineRenderer SetPosition from World to Object Space 0 Answers