Camera.main.ScreenToWorldPoint Z value

See my answer below for the solution!

////////////////////////////////////////////////////////////////////////////////////////////////

Basically what I need is an algorithm which can calculate the Z value for the Camera.main.ScreenToWorldPoint, to get the projected position of the mouse position in 3D space.

The idea is to circumvent having to make 60 ray calls per second. And here is how it is supposed to work:

The Camera angle and the distance to the object are given.
I get the mouse position with

(Input.mousePosition.y - Camera.main.WorldToScreenPoint(transform.position).y)/Screen.height

This gives me values where 0,0 is the transform.position.

Next step would be to calculate the camera z projection values, which I failed to do.

Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance + [calculated mouse.y + something]));

If the camera would project orthographically, everything would be significantly easier.
The ray to the object can be calculated by
root(distance^2 + mouse.y^2)
but then the hard part is to figure out the green ray length (when mouse.y is positive), or violet ray (mouse.y is negative), which by adding/subtracting to the original z distance would give us the projection.

Any help would be greatly appreciated.

PS: And please let me know if I am making this to complicated and there is a nice and easy function which does this automatically (beside actual ray tracing :wink:

Description Update -----------------------------------------------------------------------------

Let me first describe what I am creating:
I am doing a Diablo style game, where the camera looks from a fixed angle down on the player. The player movement is controlled via WASD and the player orientation follows the mouse cursor.

The player rotation is achieved by calculation the 2D position of the mouse and subtracting those values from transform.position

         // Mouse controlling rotation
		Vector2 mouseP = Input.mousePosition;
		Vector2 centerP = Camera.main.WorldToScreenPoint(transform.position);
		
		Vector3 directionOld = new Vector3((mouseP.x - centerP.x), 0, (mouseP.y - centerP.y));
		Debug.DrawLine (transform.position, directionOld, Color.red);
		
		if (Quaternion.LookRotation(directionOld) != transform.rotation) {
			float speed = rotSpeed * Time.deltaTime * RSPEED;
			transform.rotation = Quaternion.RotateTowards( transform.rotation, Quaternion.LookRotation(directionOld), speed);
		}

This works just fine, except if you are planing to implement some sort of long range combat. In that case you will completely miss your shots. (see picture segment1)

So this solution only works to make your character look in the general direction of where you are aiming.

Next I found out about that cool function Camera.main.ScreenToWorldPoint which gives you a vector which appears to be perfectly aligned with the mouse.

		// Mouse controlling rotation
		// Old
		Vector2 mouseP = Input.mousePosition;
		Vector2 centerP = Camera.main.WorldToScreenPoint(transform.position);
		Vector3 directionOld = new Vector3((mouseP.x - centerP.x), 0, (mouseP.y - centerP.y));
		Debug.DrawLine (transform.position, directionOld, Color.red);


		// New
		float heightM = (Input.mousePosition.y - centerP.y)/Screen.height;
		Vector3 mouseW = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,
		                                                            Input.mousePosition.y, 
		                                                            distance));
		Vector3 direction = new Vector3((mouseW.x - transform.position.x), 0, (mouseW.y - transform.position.y));
		Debug.DrawLine (transform.position, mouseW, Color.yellow);

	

		if (Quaternion.LookRotation(direction) != transform.rotation) {
			float speed = rotSpeed * Time.deltaTime * RSPEED;
			transform.rotation = Quaternion.RotateTowards( transform.rotation, Quaternion.LookRotation(direction), speed);
		}

(see picture segment 2 and 3)
As you can see the yellow vector appears to be aligned but it is actually parallel to the camera, which ultimately gives us an direction vector which is slightly off.

So now the solution would be to figure out a “value” which I can add to

Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance + "value"));

This “value” needs to be the length of the green/violet lines shown in the first schematic.
This would basically give me the correct representation of my 2D mouse position in 3D space (basically a projection), which I can use to get the true rotation of my character.

This method would be resource friendly (I think a calculated value is faster than an actual physical ray trace) and secondly, I would not have to rely on a flat terrain to get the rotation. Because here the we don’t get probes of the terrain but a calculation based on an virtual plane.

I think you are making this much harder on yourself than necessary.

Make a large thin wide box collider trigger with a kinematic rigidbody, put it on a RaycastTouchTarget layer that does not interact with any other physics layers. Parent it to the player so that the top surface is at the height you want your rotation to be.

Each frame… do a raycast from the mouse position that only collides with that layer.

Get the point…

Simple, fast, easy.

"This method would be resource friendly (I think a calculated value is faster than an actual physical ray trace) and secondly, I would not have to rely on a flat terrain to get the rotation. "

Raycasts are plenty fast… definitely fast enough that one per frame is not going to hurt anything.

This will always give you the value exactly on the plane you want it on… because you made that plane and parented it to the player.

Super Easy…

I like math too :slight_smile:

Honestly, I’m not sure about the following answer, once I drew it out, it seemed a bit too simple:

If you already HAVE the values for the click distance (on the screen), click distance (in world coordinates) and the camera angle(off horizontal).
The length of those lines you are asking, let’s call it “depth” about would be:

  Opposite/adjacent=Tan(angle)
  depth/click distance=Tan(90-CameraAngle)  [I call this angle "depth angle" in the pic]
  depth = click distance * Tan(90-CameraAngle)

The problem is this depth value is in screen coordinates, not world coordinates.
So you will need to scale the depth into world coordinates.

WorldDepth = depth *  click distance(world)/click distance (on screen) 

Edit: looking at the pic again, it may turn out that the hypotenuse of the red triangle will be more useful to you than the depth. Cos(angle)=adjacent/hypotenuse … Hyp = cos(90-cameraAngle)/click distance… then scale Hyp by the same factor: clickDist(world)/clickDist(screen)

Edit2: duh- if you use clickDistance(world) in your trig functions, you DON’T need to scale afterwards.

Solved

You can close this Thread, But you can not stop me from communication with the community! Muahahaha.

IMPORTANT:

If you see Glurth, tell him that I really appreciate his support!
Oh and tell meat5000: “WTF dude? Why do you do this to me???!! We were so close!”

And here is the Working Code:

using UnityEngine;
using System.Collections;

public class MouseProjection : MonoBehaviour {

	[Tooltip("The camera angle")]
	public float angle = 30;
	[Tooltip("Distance to the target")]
	public float distance = 10;
	[Tooltip("The above values change")]
	public bool change = true;

	public Transform target;
	public Vector3 mouseProjection;

	private float a;
	private	float b;

	// Use this for initialization
	void Start () {
		float rad = (angle * Mathf.PI) / 180;
		a = Mathf.Sin (rad) * distance;
		b = Mathf.Sin (0.5f * Mathf.PI - rad) * distance;
	}
	
	// Update is called once per frame
	void Update () {

		// Camera Position
		if (change) {
			float rad = (angle * Mathf.PI) / 180;
			a = Mathf.Sin (rad) * distance;
			b = Mathf.Sin (0.5f * Mathf.PI - rad) * distance;
		}

		float x = target.position.x;
		float y = target.position.y + b;
		float z = target.position.z - a;

		transform.position = new Vector3 (x, y, z);
		transform.LookAt(target);

		// Target position on screen
		Vector2 targetOnScreen = Camera.main.WorldToScreenPoint(target.position);
		// Mouse Y-position
		float mouseY = (Input.mousePosition.y - targetOnScreen.y)/Screen.height;
		// 3D mouse position with Z = distance
		Vector3 parallelProjection = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width*0.5f, Input.mousePosition.y, distance));
		// Length of the parallelProjection to center
		float ppLength = Vector3.Magnitude (parallelProjection - target.position);

		// Ray Calculation
		float alpha = (angle)*Mathf.Deg2Rad;
		float beta = Mathf.Atan ((ppLength / distance));

		float negateiveRay = (ppLength * Mathf.Sin (alpha))/ (Mathf.Sin ((90*Mathf.Deg2Rad - alpha + beta)));
		float positiveRay = (ppLength * Mathf.Sin (alpha))/ (Mathf.Sin ((90*Mathf.Deg2Rad - alpha - beta)));
		
		float ray;
		if (mouseY >= 0) {
			ray = positiveRay;
		}else{
			ray = negateiveRay*-1;
		}

		// Length to add to distance to get the final ray from camera to projection plane 
		float l2 = ray * Mathf.Sin ((90*Mathf.Deg2Rad) - (Mathf.Atan(ppLength/distance)) );
		// This is the 3D position of your 2D mouse
		mouseProjection = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance + l2));
		Debug.DrawLine (transform.position, mouseProjection, Color.yellow);
	}
}

////////////////////////////////////OLD STUFF////////////////////////////////////////////////////

Ok I have figured out the equation needed to calculate the violet rays (First pic from op). I had the wrong impression that Camera.main.ScreenToWorldPoint’s Z component gives a spherical projection, basically I thought that Z was something like the radius from the camera position, which it is not. That function gives a flat plane, where Z is just the distance from the camera origin.

Now here is the updated schematic with some labels to better understand the formula.

Alpha = is the camera angle. This is fix (30’)

l = is the distance from the camera origin to the object. Also fixed (10)

beta = is the the angle which get calculated according the mouse x,y position (is 30’ in this case)

x = is the result which I wanted to calculate at first.

a = the actual 3D distance from the object origin to mouse.y translated to 3D. (This is only y coordinates)

c = this is the the projection-line

l’ = this is the grand prize. l - l’ gives us the z value I am looking for. (Camera.main.ScreenToWorldPoint(x,y,l-l’)

And here is how I calculate this:

39788-figure2.png

But! This only gives me the correct value at beta = 30 which occurs when the mouse is at the bottom of the screen.

Well I have at the moment no clue what I have to change…

PS: This equation only applies for the mouse position in the bottom half of the screen.

and this is the code:

float l = 10;
float alpha = 30;


float heightM = (Input.mousePosition.y - centerP.y)/Screen.height;
Vector3 parallelP = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width*0.5f,
                                                               Input.mousePosition.y, 
                                                               l));
float a = Vector3.Magnitude (parallelP - transform.position);


// Ray Calculation
float alpha = (alpha)*Mathf.Deg2Rad;
float beta = Mathf.Atan ((a / l));

float negateiveRay = (a * Mathf.Sin (alpha))/ (Mathf.Sin ((90*Mathf.Deg2Rad - alpha + beta)));
// I left out this one.
float positiveRay = negateiveRay;

// ray is x from the picture
float ray;
if (heightM >= 0) {
	ray = positiveRay;
}else{
	ray = negateiveRay;
}


float l2 = (Mathf.Sqrt (a * a - ray * ray) * Mathf.Sin(alpha)) / (Mathf.Sin (90 * Mathf.Deg2Rad)); 
Vector3 result = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,
                                                            Input.mousePosition.y, 
                                                            l-l2));