[SOLVED]constraining movement to procedural circle/sphere

I’m trying to have the mouse move a target point around what essentially amounts to a half-sphere. I originally wrote a script based on rotating a point with the target as a child of the point, but its movement was very unintuitive for my purposes. This following script works much better so far, except that I’m having trouble clamping movement on the x and z axes such that the area of possible movement forms part of a sphere. In an earlier version, I tried clamping the distance on each axis individually based on their distance from the character’s center, but that constrained the movement to a square, not a circle. How can I clamp movement on both axes at once so that the area of possible movement resembles a half-sphere?

using UnityEngine;
using System.Collections;

public class TargetMover : MonoBehaviour {

	public Transform characterCenter;
	public float moveSpeed = 12f;
	public float maxSpeed = 3f;

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
		
		Transform cameraTransform = Camera.main.transform;
		Vector3 characterPosition = characterCenter.position;

		//get x and z axis movement
		float xMovement = moveSpeed * Input.GetAxis ("Mouse X");
		xMovement = Mathf.Clamp(xMovement, -maxSpeed, maxSpeed);
		float zMovement = moveSpeed * Input.GetAxis ("Mouse Y");
		zMovement = Mathf.Clamp(zMovement, -maxSpeed, maxSpeed);
		
		// Apply movement
		transform.Translate(Vector3.forward * zMovement, cameraTransform);
		transform.Translate(Vector3.right * xMovement, cameraTransform);
		
		//calculate target's height based on distance from character
		float y = Vector3.Distance(characterPosition, transform.position);
		y *= y * 0.2f;
		y = Mathf.Clamp(y, 0, 4);
		
		//apply height
		transform.position = new Vector3(transform.position.x, y, transform.position.z);	
	}
}

Also, movement is slower at the bottom of the sphere and higher at the top, which makes sense considering how I’ve set this up. I imagine it would be pretty convoluted, but is there some formula or way to rewrite the script that would make the relationship between mouse delta movement and distance traveled across the area even across the half-sphere?

To clamp a distance to a sphere, first subtract characterCentre from transform.position. This will give you a vector that “points” from the centre to the object’s position. Then, normalise that vector (ie, set its length to 1) and then multiply the vector by the radius of the sphere. Finally, add this new vector to characterCentre again and you will get the sphere position for the object:-

Vector3 heading = transform.position - characterCentre;
Vector3 sphereHeading = heading.normalized * sphereRadius;
transform.position = characterCentre + sphereHeading;

Thanks, andeeee, so far I’ve implemented it like so:

using UnityEngine;
using System.Collections;

public class FlashlightTargetMover : MonoBehaviour {

	public Transform characterTop;
	public Transform characterBottom;
	public float moveSpeed = 12f;
	public float maxSpeed = 3f;
	public float sphereRadius = 2f;
	public float maxHeight = 1.5f;
	public float minHeight = 0f;

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
		
		Transform cameraTransform = Camera.main.transform;
		Vector3 halfSphereTop = characterTop.position;
		Vector3 characterRoot = characterBottom.position;


		//get x and z axis movement
		float xMovement = moveSpeed * Input.GetAxis ("Mouse X");
		xMovement = Mathf.Clamp(xMovement, -maxSpeed, maxSpeed);
		float zMovement = moveSpeed * Input.GetAxis ("Mouse Y");
		zMovement = Mathf.Clamp(zMovement, -maxSpeed, maxSpeed);
		
		//apply movement
		transform.Translate(Vector3.forward * zMovement, cameraTransform);
		transform.Translate(Vector3.right * xMovement, cameraTransform);
		
		//constrain movement to sphere
		Vector3 heading = transform.position - halfSphereTop;
		Vector3 sphereHeading = heading.normalized * sphereRadius;
		transform.position = halfSphereTop + sphereHeading;
		
		//clamp height
		float y = Mathf.Clamp(transform.position.y, characterRoot.y + minHeight, characterRoot.y + maxHeight);
		
		//apply height
		transform.position = new Vector3(transform.position.x, y, transform.position.z);
	}
}

And it works exactly like I want as long as the camera’s X rotation is at 0:

If i change the camera to a more top-down view, however, the area of possible movement changes with it, and the target can get slightly “stuck” on the depressed area of the half-sphere if it is pushed in that direction for a while:

I tried changing some things around in my script but nothing helped. I need the target point’s movement to be camera-relative, but the area of possible movement, and the speed it travels at, should stay the same relative to the character. Any guesses as to what I need to do differently?

I’m sorry to keep asking for help, but I’m really stumped here. I guess this is what I get for cheating my way through most of high school math. Translating the target relative to cameraTransform is clearly the issue - when i just move it relative to itself, it works the same with the camera at any X angle, but then of course I can’t change the camera’s Y angle or the target does not move at the angle it intuitively should. Is there some way to extrapolate from cameraTransform a different Transform that will allow camera-independent movement but with a consistent “sphere” (I haven’t found one) or is there a different method I should use to translate the target, or am I completely off?

okay, so it looks like i should convert the camera coordinates into world coordinates with cameraToWorldMatrix before i use them to translate. but any way i try using cameraToWorldMatrix, it just causes the camera to shake violently. there’s basically no examples of how to use cameraToWorldMatrix on the forums and I don’t understand how to apply the documentation example to this situation.

//set up transform to move the target relative to, then translate it to world space
		GameObject mainCamera = GameObject.FindWithTag ("MainCamera");
		Matrix4x4 m = mainCamera.camera.cameraToWorldMatrix;
		Transform cameraTransform = mainCamera.transform;
		cameraTransform.position = m.MultiplyVector(cameraTransform.position);

well, the solution i found was to make an object that is a child of the camera but sets its x and z rotation to always be 0 in world space, then translate relative to that. a little hacky, but as long as it works.