Rotate an object in screen space

Hey.
I’m trying to figure it out, but I have no idea how to do this.
I want to rotate the object (the armature inside it) based on the camera view and mouse position. Something like the white line gizmo on unity’s rotate tool, that rotates the object in screen space.


Is that possible? Thanks

You can temporarily duplicate the camera’s local transform properties and then use that object’s transforms (by possibly parenting it to the newly created object), but you’ll have to work out pivots and axis (if that’s needed) by yourself.

1 Like

I think that’s not possible for what I want to do. The idea is to rotate every bone in the armature as the player click and drag the mouse through it. So the player can “draw” the shape of the string in realtime.
I though that it was possible to access that white rotation line through script. It would be easy.
Thank you for your help anyway :slight_smile:

you use Quaternion.AngleAxis(angle, axis) where you give an axis that is myCamera.transform.forward.

to generate the angle, you need to compute Math.Atan2 based on two screen-space projections: your mouse coordinates and your center of rotation. basically you need an arrow between the two, so when you project them, subtract them as well, where (target - source) is an arrow toward target (your mouse position). this 2D arrow’s components are what you use to feed Math.Atan2.

to project mouse and center of rotation to screen-space, apply Camera.WorldToScreenPoint on their world coordinates.

Note that Mathf.Atan2 returns an angle in radians, and that Quaternion.AngleAxis accepts angle in degrees, so do this angle * Mathf.Rad2Deg

apply the result to a world space rotation myTransform.rotation = result
if it runs in the opposite direction change the axis to -myCamera.transform.forward

2 Likes

Do you want to contstrain the rotation to the camera’s ‘space’ in the first place though (like ‘Screen’ in 3dsmax)?

var target = (Vector2)myCamera.WorldToScreenPoint(myMouseCoords);
var rotCenter = (Vector2)myCamera.WorldToScreenPoint(myObject.transform.position); // *
var diff = target - rotCenter;
var angleDegs = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
myObject.transform.rotation = Quaternion.AngleAxis(angleDegs, myCamera.transform.forward);
  • If you want a 3D offset from your rotation center, you could add it like this
var rotCenter = (Vector2)myCamera.WorldToScreenPoint(myObject.transform.position + offsetIn3D);

Similarly, if you want an offset in 2D space, you could do it like this

var rotCenter = (Vector2)myCamera.WorldToScreenPoint(myObject.transform.position);
rotCenter += offsetIn2D;
1 Like

Btw, if you’re interested, here’s a bit of a short history.

Mathf.Atan2 is just a slightly more advanced version of the Atan, which is just a abbreviated name for arc tangent, nowadays a standard issue in any decent math library.

Ordinary tangent (Mathf.Tan) in trig entraps a powerful relationship between a sine and a cosine, namely tan(theta) = sin(theta) / cos(theta) and thus speaks of the ratio between X and Y offset, in other words, it captures the slope of the line – where tan(0) = sin(0) / cos(0) = 0 / 1 = 0, and tan(45) = 1 (X and Y are the same), then tan(90) has to be undefined (and actually not infinity, contrary to popular belief, but it’s practically similar), because cos(90) = 0.

Arc tangent, similar to its Arc sine and cosine friends, inverts the result back to angles. So if you can get the offset by doing Mathf.Cos(30) for X and Mathf.Sin(30) for Y, and slope by Mathf.Tan(30), then you should be able to do Mathf.Acos(X) or Mahf.Asin(Y) to get your 30 back.

And this should work the same for Atan as well if you do Mathf.Atan(Y/X), however(!), this will lose a vital component of the original deltas, their actual signs.

If you pay attention, Mathf.Atan accepts just one argument (as it really should, trigonometry-wise), but this makes the result of y/x ambiguous, because if there’s a minus, you can’t tell whether it was -y/x or y/-x, and well if it’s a plus, you can’t tell whether it was y/x or -y/-x, and so the result will live in some random quadrant which you’d have to determine by analyzing the actual signs, which is what we used to do back in the days without Atan2. Yes I’m that old.

And this is the actual, old school reason, why this function accepts its arguments in the y, x order. It will probably be lost in time, like tears in rain, once the AI starts to build our software.

All in all, Mathf.Atan2 is ubiquitous in cases when you have two coplanar points and want to compute an absolute angle between them. You could, in a more general (3D) case, use Quaternion.FromToRotation to get a much more robust quaternion for the same thing.

var target = myCamera.WorldToScreenPoint(myMouseCoords);
var rotCenter = myCamera.WorldToScreenPoint(myObject.transform.position);
var direction = (target - rotCenter).normalized; // we have to normalize a direction
myObject.transform.localRotation = Quaternion.FromToRotation(direction, myObject.transform.parent.forward);
// here you basically compare two directions, the other being your object's identity direction

FromToRotation does this btw (this is my recreation, not an actual implementation)

static public Quaternion FromToRotation(Vector3 dir1, Vector3 dir2) {
  float w = 1f + Vector3.Dot(dir1, dir2);
  Vector3 xyz = Vector3.Cross(dir1, dir2);
  return new Quaternion(xyz.x, xyz.y, xyz.z, w).normalized;
}

Note that there is problem with it in Unity when the directions are directly opposite to each other. It’s a bug that won’t be solved any time soon, because it seems that I’m the only fool to actually use quaternions to such an extent.

You can see from this that it makes little sense to use if your context is 2D. Atan2 is cheaper and lighter to use, although not by much.

1 Like

Thank you so much for your help. The z value of the mouse position was totally wrong and messing with the rotation. So I calculated the distance between the camera and the object and assigned that value to the z axis on mouse position.
I also found that the LookAt, does the same thing and I can also put the camera’s axis to rotate the object.

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit) && hit.transform.gameObject.tag == "Hair")
        {
            if (Input.GetMouseButton(0))
            {
                float dist = Vector3.Distance(Camera.main.transform.position, hit.transform.position);

                Vector3 mousePos = Input.mousePosition;
                mousePos.z = dist;
                Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(mousePos);

                Debug.DrawLine(Camera.main.transform.position, mouseWorldPos, Color.green);

                hit.transform.LookAt(mouseWorldPos, -Camera.main.transform.forward); 
        }
       }

It’s working, but it didn’t turned out how I imagined though. Using colliders and raycast to find what part of the hair the player is clicking, is not a good solution. I can’t make the colliders too big cause they can’t overlap each other, and I can’t make them too small, cause it gets hard to manipulate it. I guess I will have to find another solution for this.

true, LookAt could’ve helped you as well, it’s usually my first answer. oh well
it’s a boring answer tbh

I have an issue related to the rotation also. I am developing a 3d Tank game where barrel and turret are coordinated with camera forward rotation and camera is controlled by the mouse input.
My issue is that barrel and turret rotation is not sync with hull rotation. if Hull is rotated during the drive on different angled areas , turret and barrel do not follow that and collided with the hull. additionally barrel rotation is not limited to certain parameters upwards and downwards. I’m new to the C#. please someone help ME.
this is my code
//Turret Rotation right and left
// Smoothly rotate the turret towards the camera
Quaternion tuRotation = Quaternion.LookRotation(Camera.main.transform.right);
turret.transform.rotation = Quaternion.Slerp(turret.rotation, tuRotation, delay * Time.deltaTime);
//Barrel Rotation up and down
Quaternion barrelRotation = Quaternion.LookRotation(Camera.main.transform.forward);
barrel.transform.rotation = Quaternion.Slerp(barrel.rotation, barrelRotation, delay * Time.deltaTime);

What to DO??

Please create your own thread rather than hijacking an existing one.

Also, here’s how to post code on the forums: https://discussions.unity.com/t/481379

Thanks.