rakkarage's picture

ArcBall

found this arcball code online

using System;
using System.Drawing;
 
namespace arcball
{
    /*
     * This code is a part of NeHe tutorials and was converted from C++ into C#. 
     * Matrix/Vector manipulations have been updated.
     */
    public class arcball
    {	
        private const float Epsilon = 1.0e-5f;
 
        private Vector3f StVec; //Saved click vector
        private Vector3f EnVec; //Saved drag vector
        private float adjustWidth; //Mouse bounds width
        private float adjustHeight; //Mouse bounds height
 
        public arcball(float NewWidth, float NewHeight)
        {
            StVec = new Vector3f();
            EnVec = new Vector3f();
            setBounds(NewWidth, NewHeight);
        }
 
        private void mapToSphere(Point point, Vector3f vector)
        {
            Point2f tempPoint = new Point2f(point.X, point.Y);
 
            //Adjust point coords and scale down to range of [-1 ... 1]
            tempPoint.x = (tempPoint.x * this.adjustWidth) - 1.0f;
            tempPoint.y = 1.0f - (tempPoint.y * this.adjustHeight);
 
            //Compute square of the length of the vector from this point to the center
            float length = (tempPoint.x * tempPoint.x) + (tempPoint.y * tempPoint.y);
 
            //If the point is mapped outside the sphere... (length > radius squared)
            if (length > 1.0f)
            {
                //Compute a normalizing factor (radius / sqrt(length))
                float norm = (float)(1.0 / Math.Sqrt(length));
 
                //Return the "normalized" vector, a point on the sphere
                vector.x = tempPoint.x * norm;
                vector.y = tempPoint.y * norm;
                vector.z = 0.0f;
            }
            //Else it's inside
            else
            {
                //Return a vector to a point mapped inside the sphere sqrt(radius squared - length)
                vector.x = tempPoint.x;
                vector.y = tempPoint.y;
                vector.z = (float)System.Math.Sqrt(1.0f - length);
            }
        }
 
        public void setBounds(float NewWidth, float NewHeight)
        {
            //Set adjustment factor for width/height
            adjustWidth = 1.0f / ((NewWidth - 1.0f) * 0.5f);
            adjustHeight = 1.0f / ((NewHeight - 1.0f) * 0.5f);
        }
 
        //Mouse down
        public virtual void click(Point NewPt)
        {
            mapToSphere(NewPt, this.StVec);
        }
 
        //Mouse drag, calculate rotation
        public void drag(Point NewPt, Quat4f NewRot)
        {
            //Map the point to the sphere
            this.mapToSphere(NewPt, EnVec);
 
            //Return the quaternion equivalent to the rotation
            if (NewRot != null)
            {
                Vector3f Perp = new Vector3f();
 
                //Compute the vector perpendicular to the begin and end vectors
                Vector3f.cross(Perp, StVec, EnVec);
 
                //Compute the length of the perpendicular vector
                if (Perp.length() > Epsilon)
                //if its non-zero
                {
                    //We're ok, so return the perpendicular vector as the transform after all
                    NewRot.x = Perp.x;
                    NewRot.y = Perp.y;
                    NewRot.z = Perp.z;
                    //In the quaternion values, w is cosine (theta / 2), where theta is the rotation angle
                    NewRot.w = Vector3f.dot(StVec, EnVec);
                }
                //if it is zero
                else
                {
                    //The begin and end vectors coincide, so return an identity transform
                    NewRot.x = NewRot.y = NewRot.z = NewRot.w = 0.0f;
                }
            }
        }
    }
 
    public class Matrix4f
    {
        private float[,] M;
 
        public Quat4f Rotation
        {
            set
            {
                float n, s;
                float xs, ys, zs;
                float wx, wy, wz;
                float xx, xy, xz;
                float yy, yz, zz;
 
                M = new float[4, 4];
 
                n = (value.x * value.x) + (value.y * value.y) + (value.z * value.z) + (value.w * value.w);
                s = (n > 0.0f) ? 2.0f / n : 0.0f;
 
                xs = value.x * s;
                ys = value.y * s;
                zs = value.z * s;
                wx = value.w * xs;
                wy = value.w * ys;
                wz = value.w * zs;
                xx = value.x * xs;
                xy = value.x * ys;
                xz = value.x * zs;
                yy = value.y * ys;
                yz = value.y * zs;
                zz = value.z * zs;
 
                M[0, 0] = 1.0f - (yy + zz);
                M[0, 1] = xy - wz;
                M[0, 2] = xz + wy;
 
                M[1, 0] = xy + wz;
                M[1, 1] = 1.0f - (xx + zz);
                M[1, 2] = yz - wx;
 
                M[2, 0] = xz - wy;
                M[2, 1] = yz + wx;
                M[2, 2] = 1.0f - (xx + yy);
 
                M[3, 3] = 1.0f;
 
            }
        }
 
        public Matrix4f()
        {
            setIdentity();
        }
 
        public void get_Renamed(float[] dest)
        {
            int k = 0;
            for (int i = 0; i <= 3; i++)
                for (int j = 0; j <= 3; j++)
                {
                    dest[k] = this.M[j, i];
                    k++;
                }
        }
 
        public void setZero()
        {
            this.M = new float[4, 4]; // set to zero
        }
 
        public void setIdentity()
        {
            this.M = new float[4, 4]; // set to zero
            for (int i = 0; i <= 3; i++) this.M[i, i] = 1.0f;
        }
 
        public void set_Renamed(Matrix4f m1)
        {
            this.M = m1.M;
        }
 
        public void mul(Matrix4f m1, Matrix4f m2)
        {
            float[] MulMat = new float[16];
            float elMat = 0.0f;
            int k = 0;
 
            for (int i = 0; i <= 3; i++)
                for (int j = 0; j <= 3; j++)
                {
                    for (int l = 0; l <= 3; l++) elMat += m1.M[i, l] * m2.M[l, j];
                    MulMat[k] = elMat;
                    elMat = 0.0f;
                    k++;
                }
 
            k = 0;
            for (int i = 0; i <= 3; i++)
                for (int j = 0; j <= 3; j++)
                {
                    m1.M[i, j] = MulMat[k];
                    k++;
                }
        }
 
    }
 
    public class Point2f
    {
        public float x, y;
 
        public Point2f(float x, float y)
        {
            this.x = x;
            this.y = y;
        }
    }
 
    public class Quat4f
    {
        public float x, y, z, w;
    }
 
    public class Vector3f
    {
        public float x, y, z;
 
        public static void cross(Vector3f Result, Vector3f v1, Vector3f v2)
        {
            Result.x = (v1.y * v2.z) - (v1.z * v2.y);
            Result.y = (v1.z * v2.x) - (v1.x * v2.z);
            Result.z = (v1.x * v2.y) - (v1.y * v2.x);
        }
 
        public static float dot(Vector3f v1, Vector3f v2)
        {
            return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z + v2.z);
        }
 
        public virtual float length()
        {
            return (float)System.Math.Sqrt(x * x + y * y + z * z);
        }
    }
 
}

converted it to opentk

using System;
using System.Drawing;
 
using OpenTK;
using OpenTK.Graphics;
 
namespace Test
{
	public class ArcBall
	{
		private Vector3 _start = Vector3.Zero;
		private Vector3 _end = Vector3.Zero;
		private float _adjustWidth = 0.0f;
		private float _adjustHeight = 0.0f;
 
		public void Resize(float width, float height)
		{
			_adjustWidth = 1.0f / ((width - 1.0f) * 0.5f);
			_adjustHeight = 1.0f / ((height - 1.0f) * 0.5f);
		}
 
		public void Click(Vector2 point)
		{
			MapToSphere(point, ref _start);
		}
 
		public Quaternion Drag(Vector2 point)
		{
			MapToSphere(point, ref _end);
 
			Quaternion rotate = Quaternion.Identity;
			Vector3 perpendicular = Vector3.Cross(_start, _end);
			if (perpendicular.Length > float.Epsilon)
			{
				rotate.X = perpendicular.X;
				rotate.Y = perpendicular.Y;
				rotate.Z = perpendicular.Z;
				rotate.W = Vector3.Dot(_start, _end);
			}
 
			return rotate;
		}
 
		private void MapToSphere(Vector2 point, ref Vector3 vector)
		{
			point.X = (point.X * _adjustWidth) - 1.0f;
			point.Y = 1.0f - (point.Y * _adjustHeight);
 
			float length = (point.X * point.X) + (point.Y * point.Y);
 
			if (length > 1.0f)
			{
				float norm = (float)(1.0 / Math.Sqrt(length));
 
				vector.X = point.X * norm;
				vector.Y = point.Y * norm;
				vector.Z = 0.0f;
			}
			else
			{
				vector.X = point.X;
				vector.Y = point.Y;
				vector.Z = (float)Math.Sqrt(1.0f - point.Length);
			}
		}
	}
}

it does not work... but i guess i am using it wrong? or maybe i translated it wrong... arcball usually used to rotate around an object? it can be used to adjust the camera with the mouse too right? i guess i have to invert something? ya i dont know what i am doing

_m *= Matrix4.Rotate(_arcBall.Drag(new Vector2(e.X, e.Y)));


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
rakkarage's picture

o i fixed it... kinda point.Length in last line should be length

rakkarage's picture

but its still like i am outside the sphere instead of inside?

sixman9's picture

Hi rakkarage,
I know it's over a year and a half since you wrote here, but I think I've got a small idea about the usage of your code.

[I've not verified this, but I will do in my own code but,] I'm wondering should the Vector3, _start, be assignable, as this seems to signify the center of the ArcBall?

Basically, in your code, _start ALWAYS represent the point 0, 0, 0. When I use this code, as is, in my current project, it appears to rotate my OpenGL (OpenTK) object around one corner. I know that my object, a lattice of cubes, is drawn beginning at point 0, 0, 0 as the bottom left-hand corner, and expands positively along the X and Y axis (to the right and up) and negatively along the Z axis (away from the camera). So, my thinking is, that if I set _start and _end to the actual centre of that cube, i.e. half-way along the maxium value in each axis direction I mention, I should be able to have my cube spin around its center, using a simple modification to your code.

Bearing in mind that the above is simply a theory (I'm writing this at work, don't have access to my code base), I think this might help.

I'll try to verify this, this week, and I re-post my findings.

Cheers

Rich

P.S. I'm putting the word 'Trackball' into this post for search purposes (this solution appears to also be called that, if I understand correctly).

solovey's picture

Hi rakkarage. You could use my code of trackball rotation. I dont acclimatize this code to OpenTK yet.

Quaternion _rotation = new Quaternion.Identity;
double _trackballSize  = 0.8;
 
float getXnormalized(double x, double width) 
{    
     return ( (2.0d *x) / width) - 1.0d; 
}
 
float getYnormalized(double y, double height) 
{    
     return  1.od  - ((2.0d *y) / height); 
}
 
bool calcMovement(double x1, double y1, double x2, double y2)
{
                double px0 =  getXnormalized(x1, width);
                double py0 =  getYnormalized(y1, height);
 
                double px1 = getXnormalized(x2, width);
                double py1 = getYnormalized(y2, height);
                 trackball(out axis, out angle, px1, py1, px0, py0);
                Quaternion new_rotate = new Quaternion(axis, MathFunctions.ToDegrees(angle));
                _rotation = _rotation * new_rotate;
                   return true;
}
 
void trackball(out Quaternion q, double p1x, double p1y, double p2x, double p2y)
        {
            if (p1x == p2x && p1y == p2y)
            {
                //Zero rotation
                q = Quaternion.Identity;
                return;
            }
 
            //First, figure out z-coordinates for projection of P1 and P2 to
            //deformed sphere
            Vector3 p1 = new Vector3(p1x, p1y, tb_project_to_sphere(_trackballSize, p1x, p1y));
            Vector3 p2 = new Vector3(p2x, p2y, tb_project_to_sphere(_trackballSize, p2x, p2y));
 
            //Axis of rotation
            //Now, we want the cross product of P1 and P2
            Vector3 a = Vector3.Cross(p2, p1);
            a.Normalize();
            // Figure out how much to rotate around that axis.
            double t = (p2 - p1).Length() / (2.0f * _trackballSize);
 
            //Avoid problems with out-of-control values...       
            if (t > 1.0) t = 1.0;
            if (t < -1.0) t = -1.0;
            //how much to rotate about axis
            double phi = 2.0 * Math.Asin(t);
 
            q = new Quaternion(a, MathFunctions.ToDegrees(phi));
 
        }
 
void trackball(out Vector3 axis, out double angle, double p1x, double p1y, double p2x, double p2y)
        {
            //Matrix4 rotation_matrix = Matrix4.CreateFromQuaternion(_rotation);
            //Vector3 v = new Vector3(0.0f, 1.0f, 0.0f);
            //Vector3 uv = rotation_matrix * new Vector3(0.0f, 1.0f, 0.0f);
            //Vector3 sv = rotation_matrix * new Vector3(1.0f, 0.0f, 0.0f);
            //Vector3 lv = rotation_matrix * new Vector3(0.0f, 0.0f, -1.0f);
 
            //Vector3 p1 = sv * p1x + uv * p1y - lv * tb_project_to_sphere(_trackballSize, p1x, p1y);
            //Vector3 p2 = sv * p2x + uv * p2y - lv * tb_project_to_sphere(_trackballSize, p2x, p2y);
 
            //First, figure out z-coordinates for projection of P1 and P2 to
            //deformed sphere
            Vector3 p1 = new Vector3(p1x, p1y, tb_project_to_sphere(_trackballSize, p1x, p1y));
            Vector3 p2 = new Vector3(p2x, p2y, tb_project_to_sphere(_trackballSize, p2x, p2y));
 
            //Axis of rotation
            //Now, we want the cross product of P1 and P2
            axis = Vector3.Cross(p2, p1);
            axis.Normalize();
            // Figure out how much to rotate around that axis.
            double t = (p2 - p1).Length() / (2.0f * _trackballSize);
 
            //Avoid problems with out-of-control values...       
            if (t > 1.0) t = 1.0;
            if (t < -1.0) t = -1.0;
            //how much to rotate about axis
            angle = 2.0 * Math.Asin(t);
 
        }
 
float tb_project_to_sphere(double r, double x, double  y)
        {
            double z = 0;
            double z2 = 1 - x * x - y * y;
            double d = Math.Sqrt(x * x + y * y);
            if (d < r * 0.70710678118654752440d)
            {
                //Inside sphere
                z = Math.Sqrt(r * r - d * d);
            }
            else
            {
                //On hyperbola
                double t = r / 1.41421356237309504880d;
                z = t * t / d;
            }
            return z;
        }
sixman9's picture

Hi Solovey,
I've quickly converted your Trackball code to OpenTK, I think it works fine, I'm still having issues rotating my cube around its center, I've read elsewhere that I might need to do a further translation on my final matrix.

Anyway, I though I'd post the OpenTK-based code here. I've fiddled with the precision slightly (now uses less precise floats instead of doubles - means I didn't have to write so many casts (lazy)). There is an unused variable, z2, in projectTBToSphere() method , was it needed for anything?

Thanks

Big Rich

using System;
using System.Drawing;
 
using OpenTK;
 
//Found at http://www.opentk.com/node/1282
 
namespace GridModelProj
{
	public class TrackBallTK
	{
		protected int width, height;
 
		public TrackBallTK (int _width, int _height)
		{
			width = _width;
			height = _height;
		}
 
		protected Quaternion _rotation = Quaternion.Identity;
 
		protected float _trackballSize = 0.8f;
 
		protected float getXnormalized (float x, int width)
		{
			return ((2.0f * x) / width) - 1.0f;
		}
 
		protected float getYnormalized (float y, int height)
		{
			return 1.0f - ((2.0f * y) / height);
		}
 
		public Quaternion calcMovement (Vector2 point1, Vector2 point2)
		{
			point1.X = getXnormalized (point1.X, width);
			point1.Y = getYnormalized (point1.Y, height);
 
			point2.X = getXnormalized (point2.X, width);
			point2.Y = getYnormalized (point2.Y, height);
			Vector3 axis;
			float angle;
			trackball (out axis, out angle, point2, point1);
			Quaternion new_rotate = new Quaternion (axis, MathHelper.RadiansToDegrees ((float)angle));
			//Quaternion new_rotate = new Quaternion(axis, MathFunctions.ToDegrees(angle));
			return _rotation * new_rotate;
		}
 
		protected Quaternion trackball (Vector2 point1, Vector2 point2)
		{
			if (point1.X == point2.X && point1.Y == point2.Y) {
				//Zero rotation
				return Quaternion.Identity;
			}
 
			//First, figure out z-coordinates for projection of P1 and P2 to
			//deformed sphere
			Vector3 p1 = new Vector3 (point1.X, point1.Y, projectTBToSphere (_trackballSize, point1.X, point1.Y));
			Vector3 p2 = new Vector3 (point2.X, point2.Y, projectTBToSphere (_trackballSize, point2.X, point2.Y));
 
			//Axis of rotation
			//Now, we want the cross product of P1 and P2
			Vector3 a = Vector3.Cross (p2, p1);
			a.Normalize ();
			// Figure out how much to rotate around that axis.
			float t = (p2 - p1).Length / (2.0f * _trackballSize);
 
			//Avoid problems with out-of-control values...       
			if (t > 1.0f)
				t = 1.0f;
			if (t < -1.0f)
				t = -1.0f;
			//how much to rotate about axis
			float phi = (float)(2.0 * Math.Asin ((double)t));
 
			return new Quaternion (a, MathHelper.RadiansToDegrees (phi));
			//return new Quaternion(a, MathFunctions.ToDegrees(phi));
 
		}
 
		protected void trackball (out Vector3 axis, out float angle, Vector2 point1, Vector2 point2)
		{
			//Matrix4 rotation_matrix = Matrix4.CreateFromQuaternion(_rotation);
			//Vector3 v = new Vector3(0.0f, 1.0f, 0.0f);
			//Vector3 uv = rotation_matrix * new Vector3(0.0f, 1.0f, 0.0f);
			//Vector3 sv = rotation_matrix * new Vector3(1.0f, 0.0f, 0.0f);
			//Vector3 lv = rotation_matrix * new Vector3(0.0f, 0.0f, -1.0f);
 
			//Vector3 p1 = sv * point1.X + uv * point1.Y - lv * tb_project_to_sphere(_trackballSize, point1.X, point1.Y);
			//Vector3 p2 = sv * point2.X + uv * point2.Y - lv * tb_project_to_sphere(_trackballSize, point2.X, point2.Y);
 
			//First, figure out z-coordinates for projection of P1 and P2 to
			//deformed sphere
			Vector3 p1 = new Vector3 (point1.X, point1.Y, projectTBToSphere (_trackballSize, point1.X, point1.Y));
			Vector3 p2 = new Vector3 (point2.X, point2.Y, projectTBToSphere (_trackballSize, point2.X, point2.Y));
 
			//Axis of rotation
			//Now, we want the cross product of P1 and P2
			axis = Vector3.Cross (p2, p1);
			axis.Normalize ();
			// Figure out how much to rotate around that axis.
			float t = (p2 - p1).Length / (2.0f * _trackballSize);
 
			//Avoid problems with out-of-control values...       
			if (t > 1.0f)
				t = 1.0f;
			if (t < -1.0f)
				t = -1.0f;
			//how much to rotate about axis
			angle = (float)(2.0 * Math.Asin ((double)t));
 
		}
 
		protected float projectTBToSphere (float r, float x, float y)
		{
			float z = 0;
 
			// TODO - Question z2 variable usage, below
			//float z2 = 1 - x * x - y * y;
 
			float d = (float)Math.Sqrt ((double)(x * x + y * y));
			if (d < r * 0.70710678118654752440d) {
				//Inside sphere
				z = (float)Math.Sqrt ((double)(r * r - d * d));
			} else {
				//On hyperbola
				float t = (float)(r / 1.41421356237309504880d);
				z = t * t / d;
			}
			return z;
		}
	}
}
solovey's picture

Hi, rakkarage. To get a final matrix you need to make some multiplies. I mean translate to center and translate to distance where you camera stand.

public override Matrix4 GetMatrix()
{
       return Matrix4.CreateTranslate(0, 0, -_distance) *
                Matrix4.CreateFromQuaternion(getRotation()) *
                Matrix4.CreateTranslate(-_center);
}

Where _center is the vector loocking at the point arround wich you are rotating.
_distance - I think it's clear)

If I understood correctly, you want to rotate camera arround the point. Did I?

elaverick's picture

Sorry for the thread-o-mancy but has anyone ever got the original code to work properly? I've got it implemented at the moment but when I click and drag I keep getting reset to the same point on my object (I.E. I can rotate it 30 degrees left or right but as soon as I click and drag on a new location I start dragging from the original point again). I'm wondering if it's just how I've implemented it that' is causing the problem. Currently I have a global Matrix4 called arcRotationMatrix which is acted upon when I mouseDown and when I mouseMove (with a button depressed). I then GL.MultMatrix with the arcRotationMatrix in the main rendering loop... have I just got this backwards or is it a problem with the code itself?

AftPeakTank's picture

So, could you please post the final code?

It will be very usufull.