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.

Instead of

float dist = (transform.position - myCamera.transform.position).z;


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 :wink: ViewportToWorldPoint needs the distance from the camera origin afaik.

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.

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 :slight_smile:

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 :slight_smile:


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;