# 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)
{
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;

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)));`

## Comment viewing options

### Re: ArcBall

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

### Re: ArcBall

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

### Re: ArcBall - Are _start/_end Vector3's the rotation's center?

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).

### Re: ArcBall

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;
}```

### Re: ArcBall - Solovey's Trackball code converted to OpenTK

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;
}
}
}```

### Re: ArcBall

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?

### Re: ArcBall

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?

### Re: ArcBall

So, could you please post the final code?

It will be very usufull.