I need help getting the Z position from a direction (vector3's)

I’ve been working on this problem for a very long time and I’ve almost given up. Seriously, I don’t even want to talk about how long this is taking me.

I’m attempting a 3d camera orbit function. The problem is the camera is in negative local Z space so when I do: Z/Distance for my Z calculation, the sign is always negative! It does not create that nice orbiting feature! I need the camera too smoothly transition from negative Z space to positive and back again if the player so desires.

Some of the code I’m about to supply you is not used, it is a holdover from when I was debugging/testing things out. I just thought I would post the naked code, with few if any changes from the original source.

My code:

	bool gameStart = true;
		bool inPositiveZSpace = false;

		public Vector3 findAngleFromDir (Vector3 posA, Vector3 posB)
		{
				Vector3 dir = posB;
				float horizontal;
				float vertical;
				float forward;
				float dist = Vector3.Distance (posB, Vector3.zero); //Find hypotenuse/distance
				print ("Dir computation = " + (dir / dist));
				//These are ARCCOS calculations
				horizontal = (dir.x / dist); //ArcFoo means the inverse function //RETURNS ONE. POSSIBLY WHY Mathf.COS RETURNS NAN? INVALID INPUT RANGE?
				vertical = (dir.y / dist); //Multiplying by Rad2Deg converts the return value from Mathf.ACos to degrees!
				forward = -(dir.z / dist);
				//+ " Vertical = " +  vertical
				print ("Dir =  " + dir + " Distance = " + dist + "  Horizon = " + horizontal + " Vertical = " + vertical + " Forward = " + forward);
				return new Vector3 (horizontal, vertical, forward);
		}

		public Vector3 findCirclePoint (Vector3 angles, Vector2 input, float r)
		{
			float dist = Vector2.Distance (input, Vector2.zero);
			print ("DISTANCE INPUT = " + dist);
			float horizon, vertic, acosV, acosH, zSign;
				horizon = input.x / dist;
				vertic = input.y / dist;
			acosV = Mathf.Acos(angles.y) * Mathf.Rad2Deg;
			acosH = Mathf.Acos (angles.x) * Mathf.Rad2Deg;
			zSign = Mathf.Sign(angles.z);
		if ((acosH >= 180 || acosV >= 180) && !inPositiveZSpace) { //If aligned with an axis reverse the sign of the Z component
						angles.z = +angles.z;
						inPositiveZSpace = true;
						print ("In POSITIVE z space!");
		} else if ((acosH < 180 || acosV < 180) && inPositiveZSpace){
						angles.z = -angles.z;
						inPositiveZSpace = false;
						print ("In negative z space!");
				}
		print ("Angles.x = " + acosH + " Angles.y = " + acosV);
		return (new Vector3 ((angles.x + horizon), (angles.y + vertic), angles.z) * r);
		}

		
		public void panCamera ()
		//To solve this I need to find out whats wrong with the Z component computation. Its probably the sign. Or I need to graph a sphere somehow...
		{  //Third person orbit function.
				print ("==============[START OF FUNCTION CALL]==============");
				Vector3 lookAtPos = player.transform.localPosition;
				Vector3 cameraPos = myCamera.transform.localPosition;
				print ("LookatPos = " + lookAtPos + " cameraPos = " + cameraPos);
				if (gameStart) {
						float dist = Vector3.Distance (cameraPos, lookAtPos); //Find the distance between camera and view target

						Vector3 initialPos = cameraPos * ((camDistance / 2) / dist); //Scale the distance to the limit
						Camera.main.transform.localPosition = initialPos;
						gameStart = false;

						print ("InitialPos = " + initialPos);
				} else {

			Vector3 angles = findAngleFromDir (lookAtPos, cameraPos);
			Vector2 localMInput = relativeMInput / (screenSize * maxMouseDist) * camDistance;

					//	angles = new Vector3(angles.x + localMInput.x, angles.y + localMInput.y, angles.z);
						
						Vector3 newPos = findCirclePoint (angles, localMInput, camDistance / 2);
						Camera.main.transform.localPosition = newPos;
						Camera.main.transform.LookAt (player.transform.position);
						print ("local mouse input = " + localMInput + " angles = " + angles );
						print ("Newpos = " + newPos);
				}
		}

What am I doing wrong?

So if I understand you correctly, you want the camera to move in a circle around a point, and you want the camera to look at the center.

The idea is to use the parametric equation of a circle. (I highly recommend learning about parametric equations, if you haven’t already. They are very useful.)

A circle can be defined using a center, a radius, and a normal vector. In the picture below, n (blue arrow) is the normal vector, r (red) is the radius, and the green dot is the center of the circle.

49633-circle.png

The magenta lines labeled “X” and “Y” are any two vectors that are perpendicular to each other and the normal vector. It doesn’t matter what vectors you choose, just that they’re perpendicular.

This snippet of code gives you some perpendicular vector given a vector:

public Vector3 getPerpendicularVector(Vector3 v) {
    float epsilon = 0.00000001f; // For float equality checks

    if (v == Vector3.zero) {
        return Vector3.zero;
    }
    else if (Mathf.Abs(v.x) < epsilon) {
        return new Vector3(1, 0, 0);
    }
    else if (Mathf.Abs(v.y) < epsilon) {
        return new Vector3(0, 1, 0);
    }
    else if (Mathf.Abs(v.z) < epsilon) {
        return new Vector3(0, 0, 1);
    }
    else {
        return new Vector3(1, 1, -(v.x + v.y) / v.z);
    }
}

To get the second perpendicular vector, just take the cross product between the normal and the vector that you obtain from the above code. An example is included below.

With this, you basically have all you need to move the camera in a circular path, but the camera will always be facing the same way. To address this, there is a convenient function in transform called LookAt that does exactly what it sounds like. (It points the forward direction of the transform to whatever point you want.)

With this in mind, here is a working script that you can attach to a camera to have it move in a circle around a point. You can specify the center, normal and radius in the inspector. Excluding the code that gets a perpendicular vector, there are only 4 important lines of code that you have to understand.

using UnityEngine;
using System.Collections;

public class MoveAroundPoint : MonoBehaviour {

    public Vector3 circleCenter;
    public Vector3 circleNormal;
    public float circleRadius;

    private Vector3 circleX;
    private Vector3 circleY;


	void Start () {
        // Get the first perpendicular vector "X"
        circleX = Vector3.Normalize(getPerpendicularVector(circleNormal));

        // Get the second perpendicular vector "Y"
        circleY = Vector3.Normalize(Vector3.Cross(circleX, circleNormal));
	}

	void Update () {
        float t = Time.time;

        // Set the position of the camera
        transform.position = circleCenter + (circleY * Mathf.Sin(t) + circleX * Mathf.Cos(t)) * circleRadius;

        // Make the camera look at the center of the circle
        transform.LookAt(circleCenter);
	}

    public Vector3 getPerpendicularVector(Vector3 v) {
        float epsilon = 0.00000001f; // For float equality checks

        if (v == Vector3.zero) {
            return Vector3.zero;
        }
        else if (Mathf.Abs(v.x) < epsilon) {
            return new Vector3(1, 0, 0);
        }
        else if (Mathf.Abs(v.y) < epsilon) {
            return new Vector3(0, 1, 0);
        }
        else if (Mathf.Abs(v.z) < epsilon) {
            return new Vector3(0, 0, 1);
        }
        else {
            return new Vector3(1, 1, -(v.x + v.y) / v.z);
        }
    }
}

I feel you pain. I had a bitch of a tiem fi guring this out; and I never did. I did find that there was a unity js mouselook script I could derrive my needs from. Here is the releveent portion of my port:

// stuff we need for a few things
		float distance = Vector3.Distance(transform.position, transform.parent.position);	
		
		// get our input data
		float xDelta = 0;
		float yDelta = 0;
		
		// click drag input
		if(Input.GetMouseButton(0)) {
			xDelta = Input.GetAxis( "Mouse X" );
			yDelta = Input.GetAxis( "Mouse Y" );
		} else {
			if(Input.GetKey(KeyCode.UpArrow)) {
				xDelta = 1;
			} else if(Input.GetKey(KeyCode.DownArrow)) {
				xDelta = -1;
			}
			
			if(Input.GetKey(KeyCode.RightArrow)) {
				yDelta = 1;
			} else if(Input.GetKey(KeyCode.LeftArrow)) {
				yDelta = -1;
			}
		} 		
		
		// click and drag
		if((xDelta != 0 || yDelta != 0)) {	
			// code in this block is a port of MouseOrbit.js.
			// I really wish I understood how it worked.
			xAngle += xDelta * translateSpeed * 0.02f;
			yAngle -= yDelta * (translateSpeed / 2) * 0.02f;

			if(yAngle > 90) {
				yAngle = 90;
			} else if(yAngle < -90) {
				yAngle = -90;
			}
			
			if (yAngle < -360)
				yAngle += 360;
			if (yAngle > 360)
				yAngle -= 360;
			Mathf.Clamp (yAngle, -20, 80);					
        
	        transform.rotation = Quaternion.Euler(yAngle, xAngle, 0);
	        transform.position = transform.rotation * new Vector3(0.0f, 0.0f, -distance) + transform.parent.position;
		}	
		
		
		
		// zoom input
		float zoomDelta = 0;
		if(Input.GetAxis("Mouse ScrollWheel") != 0) {
			zoomDelta = Input.GetAxis("Mouse ScrollWheel");
		} else if(Input.GetKey(KeyCode.KeypadPlus)) {
			zoomDelta = 0.1f;
		} else if(Input.GetKey(KeyCode.KeypadMinus)) {
			zoomDelta = -0.1f;
		}		
		
		// zoom		
		if(zoomDelta != 0) {						
			transform.Translate(0, 0, zoomDelta * zoomSpeed);
		} 

Note that xAngle and yAngle are members of the class.

Now, my need was to click and drag a sphere such that the camera moved around it, and not the sphere being moved by me.

Seeing has that is essentially an orbit, I think you can find the solution in the code provided.

It is worth noting that my world was centered at 0,0,0; so I am unsure if this will address your problem out of the box.

An easy way also is to child the camera to an empty with an offset, look at the empty, place the empty right at the player and rotate that. Will keep the camera at a nice distance.