- Home /
Relative rotation offset?
This script rotates a rigidbody's transform.up to look at the mouse position on click, but the problem is I want it to only rotate relative to the current rotation when dragging the mouse, instead of rotating the transform.up to aim directly at it. How can I do this?
using UnityEngine;
using System.Collections;
public class RotateTowards : MonoBehaviour
{
public float smoothing = 0.3f;
private Camera mainCamera;
private Vector3 targetDir;
void Awake ()
{
targetDir = Vector3.zero;
mainCamera = Camera.main;
}
void Update ()
{
Ray ray = mainCamera.ScreenPointToRay (Input.mousePosition);
Vector3 mousePosition = ray.origin + (ray.direction * 10);
targetDir = (mousePosition - transform.position);
}
void FixedUpdate ()
{
if (Input.GetMouseButton(0))
{
if (targetDir == Vector3.zero)
{
rigidbody.angularVelocity = Vector3.zero;
}
else
{
float angularSpeed = AngleAroundAxis (transform.up, targetDir, Vector3.forward);
rigidbody.angularVelocity = (Vector3.forward * angularSpeed * smoothing);
}
}
}
static float AngleAroundAxis (Vector3 dirA, Vector3 dirB, Vector3 axis)
{
dirA = dirA - Vector3.Project (dirA, axis);
dirB = dirB - Vector3.Project (dirB, axis);
float angle = Vector3.Angle (dirA, dirB);
return angle * (Vector3.Dot (axis, Vector3.Cross (dirA, dirB)) < 0 ? -1 : 1);
}
}
I'm unclear about your question. Right now this code uses 'Input.Get$$anonymous$$ouseButton(0)' which is true for every frame the mouse button held down. Are you saying you want it to delay rotation until the mouse is moved...to not move if the button is just clicked?
Sorry for not explaining it properly.
I don't want the transform.up to snap to the target direction when clicking. I only want to add to the current rotation while dragging the mouse. Hope that makes sense.
Answer by IsaiahKelly · Jan 02, 2014 at 10:12 PM
Here's how I solved my problem thanks to Robertbu's example code.
I could only get it to work correctly by placing most of the code in Update() and just using FixedUpdate() to apply the velocity changes. This seems to work perfectly, but please let me know if you think there's a better way.
using UnityEngine;
using System.Collections;
public class RotateTowards : MonoBehaviour
{
public float smoothing = 0.3f;
private Camera mainCamera;
private Vector3 targetDir;
private Quaternion offset;
private float angularSpeed;
void Awake ()
{
mainCamera = Camera.main;
}
void Update ()
{
if (Input.GetMouseButtonDown(0))
{
rigidbody.angularVelocity = Vector3.zero;
targetDir = MouseDirection();
offset = Quaternion.FromToRotation (transform.up, targetDir);
}
if (Input.GetMouseButton(0))
{
targetDir = MouseDirection();
angularSpeed = AngleAroundAxis (offset * transform.up, targetDir, Vector3.forward);
}
}
void FixedUpdate ()
{
if (Input.GetMouseButton(0))
rigidbody.angularVelocity = (Vector3.forward * angularSpeed * smoothing);
}
static float AngleAroundAxis (Vector3 dirA, Vector3 dirB, Vector3 axis)
{
dirA = dirA - Vector3.Project (dirA, axis);
dirB = dirB - Vector3.Project (dirB, axis);
float angle = Vector3.Angle (dirA, dirB);
return angle * (Vector3.Dot (axis, Vector3.Cross (dirA, dirB)) < 0 ? -1 : 1);
}
Vector3 MouseDirection ()
{
Vector3 mousePos = new Vector3 (Input.mousePosition.x, Input.mousePosition.y, 10.0f);
Vector3 mouseWorldPos = mainCamera.ScreenToWorldPoint (mousePos);
Vector3 dir = mouseWorldPos - transform.position;
return dir;
}
}
If your changes work for you, I see nothing wrong with them. I played for awhile and could not reproduce the problems you outlined in the original code, but in theory it is possible that a fixed update misses the frame when the mouse button goes down so that the rotation does not get set. If this is the problem, then your rewrite should solve it. Note I reiterate the restrictions on how this code is currently written:
The camera must have rotation of (0,0,0).
The distance 'z' distance between the camera and the subject must be exactly 10 units.
The are alternate solutions if your app cannot live within these restrictions.
Answer by robertbu · Jan 01, 2014 at 11:34 PM
I think this is what you want. At the top of the file:
private Vector3 downPos;
Add to FixedUpdate():
if (Input.GetMouseButtonDown(0)) {
downPos = Input.mousePosition;
}
Then modify line 26:
if (Input.GetMouseButton(0) && Vector3.Distance(downPos, Input.mousePosition) > threshold) {
'threshold' is some small value like 3. And if you are targeting multiple platforms, then you may want to calculate 'threshold' based on Screen.dpi.
The idea here is that the mouse has to be some small distance from where it was first clicked before it starts tracking. There is one hole in this logic that I don't think matters in practice. If the user drags away form 'downPos' and then drags back to 'downPos', it will stop trying to rotate the object while the position is very near 'downPos'. If this becomes a problem, it is easy to fix.
I really appreciate your help, but I guess I'm still not explaining it right. Very sorry about that.
$$anonymous$$y problem is not with mouse input, but what happens after. Essentially, I'm trying to use the mouse to grab/spin the rigidbody relative to it's current rotation. The problem is with this part:
float angularSpeed = AngleAroundAxis (transform.up, targetDir, Vector3.forward);
This rotates the transform.up to the targetDir and means that if the transform.up is ai$$anonymous$$g to the right and I click to the left, it will rotate 180 degrees. I don't want that. I want it to keep it's current rotation when I click and only add to it as the targetDir (mouse position) changes when holding the mouse down.
I think I need to apply some kind of angle offset to accomplish this. I can get the angle differences with something like:
if (Input.Get$$anonymous$$ouseButtonDown(0))
{
angleOffset = Vector3.Angle (transform.up, targetDir);
}
But I have no idea how to apply this as an offset. :(
Your calculation of an angle offset the way you indicate won't work, mostly because Vector3.Angle() returns an unsigned value. Below is a script that I think is what you want. Note this script assumes that the camera is 1) axes aligned, 2) facing positive 'z', and 3) the camera's 'z' coordinate is 10 units away from the objects 'z' coordinate.
using UnityEngine;
using System.Collections;
public class RotateTowards : $$anonymous$$onoBehaviour
{
public float smoothing = 0.3f;
private Camera mainCamera;
private Vector3 targetDir;
private Quaternion q;
void Awake ()
{
targetDir = Vector3.zero;
mainCamera = Camera.main;
}
void FixedUpdate ()
{ if (Input.Get$$anonymous$$ouseButtonDown (0)) {
Vector3 pos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10.0f);
pos = mainCamera.ScreenToWorldPoint(pos);
targetDir = pos - transform.position;
q = Quaternion.FromToRotation (transform.up, targetDir);
}
if (Input.Get$$anonymous$$ouseButton(0))
{
Vector3 pos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10.0f);
pos = mainCamera.ScreenToWorldPoint(pos);
targetDir = pos - transform.position;
if (targetDir == Vector3.zero)
{
rigidbody.angularVelocity = Vector3.zero;
}
else
{
float angularSpeed = AngleAroundAxis (q * transform.up, targetDir, Vector3.forward);
rigidbody.angularVelocity = (Vector3.forward * angularSpeed * smoothing);
}
}
}
static float AngleAroundAxis (Vector3 dirA, Vector3 dirB, Vector3 axis)
{
dirA = dirA - Vector3.Project (dirA, axis);
dirB = dirB - Vector3.Project (dirB, axis);
float angle = Vector3.Angle (dirA, dirB);
return angle * (Vector3.Dot (axis, Vector3.Cross (dirA, dirB)) < 0 ? -1 : 1);
}
}
I also redid how the mouse position is converted from screen to world coordinates. Note when you create a ray with Camera.ScreenPointToRay(), the origin starts at the near clip plane, not the camera origin. Plus I don't like that the calculation was done 1) every frame even when the mouse was held down, and 2) was done in Update() (which has a different frame rate than FixedUpdate()).
Thanks! This is almost exactly what I want, but there's something wrong with the offset calculation. Sometimes it follows the mouse from the current offset correctly, other times it rotates to the mouse on click etc.
Okay. I think the problem might be with the applied angular velocity and not the offset calculation. The AngleAroundAxis function seems to be adding force when it shouldn't. I tried setting angular velocity to zero on mouse down, but that didn't help.
Hmm.. Changing FixedUpdate to Update solves the problem, but I know this is the wrong place to update physics objects.