Rotate sphere with mouse problem

Hi, I’m trying to rotate a sphere with the mouse.

I have the following script, but it rotates totally uncontrolled when I hold down the mouse. Can’t seem to figure out what’s wrong. Any help/input is appreciated.

if (isMouseDown)
{
  if (castRay(Input.mousePosition))
  {
    Vector3 dir = hitInfo.point - transform.position;
    dir.Normalize();
    float angle = Vector3.Angle(transform.forward, dir);
    Vector3 crossProd = Vector3.Cross(transform.forward, dir);
    transform.Rotate(crossProd, angle);
  }
}
bool castRay(Vector3 pos)
{
  Ray ray = Camera.main.ScreenPointToRay(pos);
  if (Physics.Raycast (ray, out hitInfo))
    return true;
  return false;
}

Ehh, well I am on the exact same problem. But more precisely I want to rotate my spherical object as it was a trackball.
So this means that independently of its axes it always should rotate left when you drag left and up when you drag up.

At the moment I am using this script to rotate it (I am just putting my code into yours):

if (isMouseDown)
{
  if (castRay(Input.mousePosition))
  {
    whateverGameObj.transform.rotation *= Quaternion.Euler(Input.mousePosition.y, -Input.mousePosition.x, 0);
  }
}

However this code rotates as expected only when spherical object is facing the user. Once it turns 180°, either vertical or horizontal axis rotation get inversed compared to drag direction. And this is not a trackball behaviour.

So if anyone else could achieve a true trackball rotation, it would be nice to hear about his/her solution.

Thanks a lot.
Greg

EDIT:
I made one mistake.

instead of using the Input.mousePosition in the Quaternion.Euler method you must use the Event.current.delta.x and Event.current.delta.y of the MouseDrag event.

So

public void OnGUI()
{
   if (Event.current.type == EventType.MouseDrag)
   {
      rotate(Event.current.delta);
   }
}

public void rotate(Vector2 rot)
{
    whateverGameObj.transform.rotation *= Quaternion.Euler(rot.y, -rot.x, 0);
}

In case your axis are inverted, just change the sign of the rot.x or rot.y

Hey Guys I am almost at it!!!

The approach of Immortalis is the good one, but there are some stuff that is missing!
So here is the code that is almost perfect. There is a small lag when changing directions without releasing the mouse button. Please help me fine tune it!

//Variables that we will need
Vector3 center;
Vector3 axis;
float angle;

RaycastHit oldHitPoint;
RaycastHit newHitPoint;

public void OnGUI()
{
   if (Event.current.type == EventType.mouseDown)
   {
Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out oldHitPoint);
center = whateverGameObj.transform.position;
   }

   if (Event.current.type == EventType.MouseDrag)
   {
       rotate(Input.mousePosition);
   }
}

public void rotate(Vector3 mCurrentPos)
{ 

Physics.Raycast(camera.ScreenPointToRay(mCurrentPos), out newHitPoint);
        axis = Vector3.Cross(newHitPoint.point - center, oldHitPoint.point - center);
        angle = Vector3.Dot(newHitPoint.point, oldHitPoint.point) / 0.04f; //this is the radius of the trackball. If you want that it turn faster, decrease this float

        whateverGameObj.transform.rotation = Quaternion.AngleAxis(-angle, axis) * skeleton.transform.rotation;
}

OK, now it goes as a true trackball, but there is that lag when changing directions.

OMG! Just tried something and I have to say…
Well I was overcomplicating.

So here is the final stuff working 100% trackball! Freaking simple!

public void OnGUI()
{
  if (Event.current.type == EventType.MouseDrag)
  {
    if (Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out oldHitPoint))
    {
       rotate(Event.current.delta);
    }
  }
}

 public void rotate(Vector2 mCurrentPos)
    {
        whateverGameObj.transform.rotation = Quaternion.Euler(mCurrentPos.y, -mCurrentPos.x, 0) * whateverGameObj.transform.rotation;
    }

And thats it!
:lol:

Given that it involved no GUI components at all you should call it in Update() or PhysicsUpdate().

But very cool. :slight_smile:
Mind if I pinch it?

No problem at all!!

But the problem is that I couldn’t get Input.GetMouseButtonUp(1) work in Update()
It says Object reference not set to an instance of an object.

I have never been able to get MouseButtonUp to work in any situation myself. :stuck_out_tongue:

greg767: That’s awsome! Just got home and tried your simplified version out and as you said 100% trackball - exactly what I was aiming for. Though I’m still puzzled why my first script was all acting crazy…?

I’ve made a non-GUI version. Though I had trouble checking if two vectors were identical by value, so I made my own function, but there might be a builtin function in unity though… But this is the complete script without GUI.

Vector3 previousMousePosition;
bool isMouseDown;

// Update is called once per frame
void Update () 
{		
  if (isMouseDragged())
  {			
    if (castRay(Input.mousePosition))
    {
      Vector3 mouseDelta = Input.mousePosition - previousMousePosition;
      gameObject.transform.rotation = Quaternion.Euler(mouseDelta.y, -mouseDelta.x, 0) * gameObject.transform.rotation;
    }
  }		
  previousMousePosition = Input.mousePosition;
}
	
bool castRay(Vector3 pos)
{
  Ray ray = Camera.main.ScreenPointToRay(pos);
  if (Physics.Raycast (ray))
    return true;
  return false;
}
	
void OnMouseDown()
{
  isMouseDown = true;
}
	
void OnMouseUp()
{
  isMouseDown = false;
}
	
bool isMouseDragged()
{
  if (isMouseDown  !vector3Equals(previousMousePosition, Input.mousePosition))
    return true;
  return false;
}
	
bool vector3Equals(Vector3 vec1, Vector3 vec2)
{
  if (vec1.x == vec2.x  vec1.y == vec2.y  vec1.z == vec2.z)
    return true;
  return false;
}

Very cool little script. Thanks. :slight_smile:

Hey Immortalis,

Any idea why when I attach this script to an object, it seems to rotate in the opposite of the expected direction when moving up/down?

Left / Right seems to work fine, but when trying to change the forward/back pitch of the object (from the camera view) it seems to be flipped.

amcclay: I think it’s because your camera is placed so it looks down the negative z-axis. Try and rotate and move your camera so it looks down the positive z-axis instead.

Wow. Bingo! You got it. Just moved my camera to the other side of the scene and reoriented it and it works great!

Thanks so much! Now… for the real fun. I want to attempt to put some inertia on it so it has a more natural start / stop to it. :slight_smile:

One more Q.

Can anybody see a way to integrate this type of behavior into the previously posted script?

http://forum.unity3d.com//viewtopic.php?t=14293

Basically that example allows for a very natural feeling “spin” for the object.

Don’t know about what you’re referring to, but if you want to add spin (which was my goal as well) you can use the script I ended up doing. The factor variable can be adjusted for the amount of acceleration you want to add.

public class Rotation : MonoBehaviour 
{
	int factor = 30; //adjust as desired
	Vector3 previousMousePosition;
	bool isMouseDown;
	Vector3 mouseDelta;
	
	// Update is called once per frame
	void Update () 
	{	
		if (isMouseDown)
		{
			rigidbody.angularVelocity = Vector3.zero;
		}
		
		if (isMouseDragged())
		{	
			if (castRay(Input.mousePosition))
			{			
				mouseDelta = Input.mousePosition - previousMousePosition;
				mouseDelta *= 0.1f; // reduction of rotation "speed".
				transform.rotation = Quaternion.Euler(mouseDelta.y, -mouseDelta.x, 0) * transform.rotation;					
			}
		}
		else if (!isMouseDown)
		{			
			rigidbody.AddTorque(factor* mouseDelta.y, -mouseDelta.x * factor, 0);
			mouseDelta = Vector3.zero; // reset
		}
		previousMousePosition = Input.mousePosition;		
	}
	
	bool castRay(Vector3 pos)
	{
		Ray ray = Camera.main.ScreenPointToRay(pos);
		if (Physics.Raycast (ray))
			return true;			
		return false;
	}
	
	void OnMouseDown()
	{
		isMouseDown = true;
	}
	
	void OnMouseUp()
	{
		isMouseDown = false;
	}
	
	bool isMouseDragged()
	{
		if (isMouseDown  !vector3Equals(previousMousePosition, Input.mousePosition))
			return true;
		return false;
	}
	
	bool vector3Equals(Vector3 vec1, Vector3 vec2)
	{
		if (vec1.x == vec2.x  vec1.y == vec2.y  vec1.z == vec2.z)
			return true;
		return false;
	}
}

Very interesting post, well done !

My problem is that I am trying to do a similar thing to your trackball but with a flat disc that will rotate all the way around (x+y) with mouse drag. I have tried to edit the code to suit my needs, but it just wont work. What I am after is similar to if you had a dart board on the wall and being able to rotate it clockwise/anti-clockwise with the mouse dragging around the radius.

Any idea’s of what I would need to change would be a great help?

If you place a hinge joint at the centre of the disc, you might be able to use the DragRigidbody script (menu Component > Scripts > Drag Rigidbody) to rotate it with physics.