- Home /
ViewportToWorldPoint relative to camera angle
Using the very helpful tip from this thread, I was able to lock my object to remain inside the viewport, and it works with both orthographic and perspective cameras, with one issue.
I need this using a perspective camera at an angle. The camera's X axis rotation is -45 degrees. This seems to mess up the position logic below. Any idea why I'm missing?
float dist = (transform.position - myCamera.transform.position).z;
float leftBorder = myCamera.ViewportToWorldPoint(new Vector3(0,0,dist)).x;
float rightBorder = myCamera.ViewportToWorldPoint(new Vector3(1,0,dist)).x;
float bottomBorder = myCamera.ViewportToWorldPoint(new Vector3(0,0,dist)).y;
float topBorder = myCamera.ViewportToWorldPoint(new Vector3(0,1,dist)).y;
float x = Mathf.Clamp(transform.position.x,leftBorder,rightBorder);
float y = Mathf.Clamp(transform.position.y,bottomBorder,topBorder);
transform.position = new Vector3(x,y, transform.position.z);
For clarity, when the camera is level the object is prevented from moving off camera, it just 'sticks' as far as it can go. However when the camera is angled it seems to change my z depth. Dragging the object to the top does prevent it from moving off camera, but it starts to zoom along the z-index.
I haven't got any code to fix this so I won't post it as an answer, but you need to adjust how you use the dist variable according to the camera rotation; you're actually going to have to rethink a lot of this if you want to include it in the calculations.
If you want the object to move somewhere on the camera's viewplane at a certain depth then you will have to rethink the last 3 lines; you're only adjusting the x and y and are not adjusting the z. This will mean the distance gets lost...
How exactly do you want the movement to be limited? And is the camera free-rotating/moving?
Answer by Bunny83 · May 28, 2012 at 11:51 AM
Instead of
float dist = (transform.position - myCamera.transform.position).z;
use
float dist = myCamera.transform.InverseTransformPoint(transform.position).z;
This will transform the vector in the cameras local space, so the z axis is your view direction and the distance from the camera origin parallel to the near plane. Keep in mind that the camera origin always is located behind the nearplane for perspective cameras. To get the distance from the nearplane, just subract the near value from the distance ;) ViewportToWorldPoint needs the distance from the camera origin afaik.
edit
The whole clamping process can be done like this:
Vector3 localPos = myCamera.transform.InverseTransformPoint(transform.position);
Vector3 leftBottom = myCamera.ViewportToWorldPoint(new Vector3(0,0,dist));
Vector3 rightTop = myCamera.ViewportToWorldPoint(new Vector3(1,1,dist));
leftBottom = myCamera.transform.InverseTransformPoint(leftBottom);
rightTop = myCamera.transform.InverseTransformPoint(rightTop);
float x = Mathf.Clamp( localPos.x, leftBottom.x, rightTop.x );
float y = Mathf.Clamp( localPos.y, leftBottom.y, rightTop.y );
transform.position = myCamera.transform.TransformPoint(new Vector3(x,y, localPos.z));
This will transform all coordinates into the cameras local space, clamp the position and convert back to world space at the end.
If the camera distance and orientation doesn't change it might be a bit faster to setup a Plane in Start, but haven't tried it that way ;)
This didn't quite do the trick because of some scaling issues, but InverseTransformPoint pointed me in the right direction. Thanks! :)
Answer by whydoidoit · May 28, 2012 at 11:36 AM
Ok so your problem is your are thinking in x,y,z which pretty much goes out of the window when you start rotating things. Instead you need to consider this as a 3d problem.
You've defined a plane, using the cameras viewport and the distance of the plane from the camera.
Now you want to constrain the object to lie within two vectors that define the plane. First you need to split up the transform.position so it is comprised of two parts, one for each of the axes, then you want to constrain each of those split parts so that they fall within the desired range, then put them back together again :)
Just a note, due to the floating point inaccuracies, you need to have a desiredPosition variable that holds where you want the object to be - otherwise it will probably just float off into nowhere :)
UPDATE
Used Bunny83's parallel distance calculation, clamped the dot product which I forgot in the last version (oops).
Here you go:
public Vector3 desiredPosition;
// Use this for initialization
void Start ()
{
desiredPosition = transform.position;
}
// Update is called once per frame
void Update ()
{
var myCamera = Camera.main;
//Get the distance from the camera
float dist = Mathf.Abs(myCamera.transform.InverseTransformPoint (desiredPosition).z);
//Get the coordinates of the camera at distance d
Vector3 topLeft = myCamera.ViewportToWorldPoint (new Vector3 (0, 0, dist));
Vector3 topRight = myCamera.ViewportToWorldPoint (new Vector3 (1, 0, dist));
Vector3 bottomLeft = myCamera.ViewportToWorldPoint (new Vector3 (0, 1, dist));
//Work out the two vectors that define the plane on which the object
//must remain
Vector3 vY = bottomLeft - topLeft;
Vector3 vX = topRight - topLeft;
//Project the objects coordinates onto the plane vectors
Vector3 vpX = (Mathf.Max (0, (Vector3.Dot (desiredPosition - topLeft, vX))) / vX.sqrMagnitude) * vX;
Vector3 vpY = (Mathf.Max (0, (Vector3.Dot (desiredPosition - topLeft, vY))) / vY.sqrMagnitude) * vY;
//Clamp the object to fit in the range defined by the existing vectors
vpX = Mathf.Clamp01 (vpX.magnitude / vX.magnitude) * vX;
vpY = Mathf.Clamp01 (vpY.magnitude / vY.magnitude) * vY;
//Set the transform back again
transform.position = vpX + vpY + topLeft;
}
Your answer
Follow this Question
Related Questions
Code behind 'Camera.ViewportPointToRay'? 2 Answers
ViewportToWorldPoint with multiple cameras 0 Answers
Viewport to World Coordinates 2 Answers
ViewportToWorldPoint returns same value regardless of input 1 Answer
How to resize a camera's orthoSize for an object to fit inside its rect viewport? 1 Answer