-
Arcball system
I've been trying to get a working arcball system. I've looked at several of the tutorials scattered across the web and compared thousands of lines of source code to my own. I don't know if anyone's familiar with this but if you are any help would be appreciated.
On the mousebuttondown event, Click is called, passing the class's start vector in the second parameter, drag is called on mousemovepassing the class's end vector in the second parameter, and end is called on mousebuttonup.
I believe my problem is within the MapToSphere function. The data that the function is generating to both the start and end points are (cos(45), cos(45), 0) irregardless of where i drag the mouse. Thus, no rotation occurs since there is no difference between the points. I must be missing something because I can't seem to see how this would happen.
All my matrix, quaternion, and vector math is solid so I don't think that's the problem.
Sorry if this is too confusing, but I'll clarify if I can if ther's any confusion. Thanks in advance.
Code:
void MArcBall::End(void)
{
this->m_LastRotMatrix = this->m_CurrentRotMatrix;
}
void MArcBall::SetBounds(int NewWidth, int NewHeight)
{
if((NewWidth > 1) && (NewHeight > 1))
{
this->AdjustWidth = 1 / (NewWidth / 2);
this->AdjustHeight = 1 / (NewHeight / 2);
}
}
void MArcBall::Click(int x, int y)
{
this->m_RotationAxis.SetIdentity();
this->m_CurrentRotMatrix.SetIdentity();
this->MapToSphere(x, y, &this->m_Start);
}
void MArcBall::MapToSphere(int mx, int my, MVector* NewVec)
{
double lengthsquared, x, y;
//Adjust point coords and scale down to range of [-1 ... 1]
NewVec->SetX((mx * this->AdjustWidth) - 1);
NewVec->SetY(1 - (my * this->AdjustHeight));
x = NewVec->GetX();
y = NewVec->GetY();
//Compute the square of the length of the vector to the point from the center
lengthsquared = (x * x) + (y * y);
//If the point is mapped outside of the sphere... (length > radius squared)
if (lengthsquared > 1.0f)
{
double norm = 1.0f / sqrt(lengthsquared);
//Return the "normalized" vector, a point on the sphere
//yes, i know, bad coding practice here, but i've been too lazy to write a function for this
NewVec->m_matrix.m_quat.m_matrix[0][0] *= norm;
NewVec->m_matrix.m_quat.m_matrix[1][0] *= norm;
NewVec->m_matrix.m_quat.m_matrix[2][0] = 0.0f;
}
else //Else it's on the inside
{
//Return a vector to a point mapped inside the sphere sqrt(radius squared - length)
NewVec->m_matrix.m_quat.m_matrix[2][0] = sqrt(1 - lengthsquared);
}
}
void MArcBall::Drag(int mx, int my)
{
//Map the point to the sphere
this->MapToSphere(mx, my, &this->m_End);
MVector Axis = this->m_Start.Cross(this->m_End);
//Compute the length of the perpendicular vector
if (Axis.Length() > 0) //if its non-zero
{
//We're ok, so return the perpendicular vector as the transform after all
this->m_RotationAxis.Set(
Axis.GetX(),
Axis.GetY(),
Axis.GetZ(),
this->m_Start.Dot(this->m_End)
);
}
else //if its zero
{
//The begin and end vectors coincide, so return an identity transform
this->m_RotationAxis.SetIdentity();
}
this->MQuatToMMatrix();
this->m_CurrentRotMatrix *= this->m_LastRotMatrix;
this->MakeTransformMatrix();
PrintData();
}
void MArcBall::MQuatToMMatrix(void)
{
double w = m_RotationAxis.GetW();
double x = m_RotationAxis.GetX();
double y = m_RotationAxis.GetY();
double z = m_RotationAxis.GetZ();
double Nq = x*x + y*y + z*z + w*w;
double s = (Nq > 0.0f) ? (2.0f / Nq) : 0.0f;
double xs = x*s, ys = y*s, zs = z*s;
double wx = w*xs, wy = w*ys, wz = w*zs;
double xx = x*xs, xy = x*ys, xz = x*zs;
double yy = y*ys, yz = y*zs, zz = z*zs;
m_CurrentRotMatrix.Set(0,0,1.0f - (yy + zz));
m_CurrentRotMatrix.Set(1,0,(xy - wz));
m_CurrentRotMatrix.Set(2,0,(xz + wy));
m_CurrentRotMatrix.Set(0,1,(xy + wz));
m_CurrentRotMatrix.Set(1,1,1.0f - (xx + zz));
m_CurrentRotMatrix.Set(2,1,(yz - wx));
m_CurrentRotMatrix.Set(0,2,(xz - wy));
m_CurrentRotMatrix.Set(1,2,(yz + wx));
m_CurrentRotMatrix.Set(2,2,1.0f - (xx + yy));
}
//not this
void MArcBall::MakeTransformMatrix(void)
{
this->m_Rotate[0] = this->m_CurrentRotMatrix.Get(0,0); this->m_Rotate[4] = this->m_CurrentRotMatrix.Get(0,1); this->m_Rotate[8] = this->m_CurrentRotMatrix.Get(0,2);
this->m_Rotate[1] = this->m_CurrentRotMatrix.Get(1,0); this->m_Rotate[5] = this->m_CurrentRotMatrix.Get(1,1); this->m_Rotate[9] = this->m_CurrentRotMatrix.Get(1,2);
this->m_Rotate[2] = this->m_CurrentRotMatrix.Get(2,0); this->m_Rotate[6] = this->m_CurrentRotMatrix.Get(2,1); this->m_Rotate[10] = this->m_CurrentRotMatrix.Get(2,2);
this->m_Rotate[3] =
this->m_Rotate[7] =
this->m_Rotate[11] =
this->m_Rotate[12] =
this->m_Rotate[13] =
this->m_Rotate[14] = 0;
this->m_Rotate[15] = 1;
}
}
-
I found the problem. It was a rounding error with my SetBounds function. It kept rounding the value down to zero, so i got rid of "1 / " and in my maptosphere function replaced
NewVec->SetX((mx / this->AdjustWidth) - 1);
NewVec->SetY(1 - (my / this->AdjustHeight));
with
NewVec->SetX((mx * this->AdjustWidth) - 1);
NewVec->SetY(1 - (my * this->AdjustHeight));