mirror of https://github.com/GNOME/gimp.git
529 lines
12 KiB
C
529 lines
12 KiB
C
/************************************/
|
|
/* ArcBall.c (c) Ken Shoemake, 1993 */
|
|
/* Modified by Tom Bech, 1996 */
|
|
/************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <libgimp/gimp.h>
|
|
|
|
#include "arcball.h"
|
|
|
|
/* Gloval variables */
|
|
/* ================ */
|
|
|
|
Quat qOne = { 0, 0, 0, 1 };
|
|
|
|
static HVect center;
|
|
static double radius;
|
|
static Quat qNow, qDown, qDrag;
|
|
static HVect vNow, vDown, vFrom, vTo, vrFrom, vrTo;
|
|
static HMatrix mNow, mDown;
|
|
static unsigned int showResult, dragging;
|
|
static ConstraintSet sets[NSets];
|
|
static int setSizes[NSets];
|
|
static AxisSet axisSet;
|
|
static int axisIndex;
|
|
|
|
static HMatrix mId =
|
|
{
|
|
{ 1, 0, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 0, 1, 0 },
|
|
{ 0, 0, 0, 1 }
|
|
};
|
|
|
|
static double otherAxis[][4] =
|
|
{
|
|
{-0.48, 0.80, 0.36, 1}
|
|
};
|
|
|
|
/* Externally visible methods */
|
|
/* ========================== */
|
|
|
|
void ArcBall_Init (void);
|
|
void ArcBall_Place (HVect Center,
|
|
double Radius);
|
|
void ArcBall_UseSet (AxisSet axis_Set);
|
|
void ArcBall_Update (void);
|
|
void ArcBall_Value (HMatrix m_Now);
|
|
void ArcBall_Values (double *alpha,
|
|
double *beta,
|
|
double *gamma);
|
|
void ArcBall_BeginDrag (void);
|
|
void ArcBall_EndDrag (void);
|
|
void ArcBall_Mouse (HVect v_Now);
|
|
void ArcBall_CopyMat (HMatrix inm,
|
|
HMatrix outm);
|
|
|
|
/* Internal methods */
|
|
/* ================ */
|
|
|
|
static void Qt_ToMatrix(Quat q,HMatrix out);
|
|
static Quat Qt_Conj(Quat q);
|
|
static Quat Qt_Mul(Quat qL, Quat qR);
|
|
static Quat Qt_FromBallPoints(HVect from, HVect to);
|
|
static void Qt_ToBallPoints(Quat q, HVect *arcFrom, HVect *arcTo);
|
|
|
|
static HVect V3_(double x, double y, double z);
|
|
static double V3_Norm(HVect v);
|
|
static HVect V3_Unit(HVect v);
|
|
static HVect V3_Scale(HVect v, double s);
|
|
static HVect V3_Negate(HVect v);
|
|
/*
|
|
static HVect V3_Add(HVect v1, HVect v2);
|
|
*/
|
|
static HVect V3_Sub(HVect v1, HVect v2);
|
|
static double V3_Dot(HVect v1, HVect v2);
|
|
/*
|
|
static HVect V3_Cross(HVect v1, HVect v2);
|
|
static HVect V3_Bisect(HVect v0, HVect v1);
|
|
*/
|
|
|
|
static HVect MouseOnSphere(HVect mouse, HVect ballCenter, double ballRadius);
|
|
static HVect ConstrainToAxis(HVect loose, HVect axis);
|
|
static int NearestConstraintAxis(HVect loose, HVect *axes, int nAxes);
|
|
|
|
/* Establish reasonable initial values for controller. */
|
|
/* =================================================== */
|
|
|
|
void
|
|
ArcBall_Init (void)
|
|
{
|
|
int i;
|
|
|
|
center = qOne;
|
|
radius = 1.0;
|
|
vDown = vNow = qOne;
|
|
qDown = qNow = qOne;
|
|
for (i=15; i>=0; i--)
|
|
((double *)mNow)[i] = ((double *)mDown)[i] = ((double *)mId)[i];
|
|
|
|
showResult = dragging = FALSE;
|
|
axisSet = NoAxes;
|
|
sets[CameraAxes] = mId[X];
|
|
setSizes[CameraAxes] = 3;
|
|
sets[BodyAxes] = mDown[X];
|
|
setSizes[BodyAxes] = 3;
|
|
sets[OtherAxes] = otherAxis[X];
|
|
setSizes[OtherAxes] = 1;
|
|
}
|
|
|
|
/* Set the center and size of the controller. */
|
|
/* ========================================== */
|
|
|
|
void
|
|
ArcBall_Place (HVect Center,
|
|
double Radius)
|
|
{
|
|
center = Center;
|
|
radius = Radius;
|
|
}
|
|
|
|
/* Incorporate new mouse position. */
|
|
/* =============================== */
|
|
|
|
void
|
|
ArcBall_Mouse (HVect v_Now)
|
|
{
|
|
vNow = v_Now;
|
|
}
|
|
|
|
/* Choose a constraint set, or none. */
|
|
/* ================================= */
|
|
|
|
void
|
|
ArcBall_UseSet (AxisSet axis_Set)
|
|
{
|
|
if (!dragging) axisSet = axis_Set;
|
|
}
|
|
|
|
/* Using vDown, vNow, dragging, and axisSet, compute rotation etc. */
|
|
/* =============================================================== */
|
|
|
|
void
|
|
ArcBall_Update (void)
|
|
{
|
|
int setSize = setSizes[axisSet];
|
|
HVect *set = (HVect *)(sets[axisSet]);
|
|
|
|
vFrom = MouseOnSphere(vDown, center, radius);
|
|
vTo = MouseOnSphere(vNow, center, radius);
|
|
if (dragging)
|
|
{
|
|
if (axisSet!=NoAxes)
|
|
{
|
|
vFrom = ConstrainToAxis(vFrom, set[axisIndex]);
|
|
vTo = ConstrainToAxis(vTo, set[axisIndex]);
|
|
}
|
|
qDrag = Qt_FromBallPoints(vFrom, vTo);
|
|
qNow = Qt_Mul(qDrag, qDown);
|
|
}
|
|
else
|
|
{
|
|
if (axisSet!=NoAxes) axisIndex = NearestConstraintAxis(vTo, set, setSize);
|
|
}
|
|
Qt_ToBallPoints(qDown, &vrFrom, &vrTo);
|
|
Qt_ToMatrix(Qt_Conj(qNow), mNow); /* Gives transpose for GL. */
|
|
}
|
|
|
|
/* Return rotation matrix defined by controller use. */
|
|
/* ================================================= */
|
|
|
|
void
|
|
ArcBall_Value (HMatrix m_Now)
|
|
{
|
|
ArcBall_CopyMat (mNow, m_Now);
|
|
}
|
|
|
|
/* Extract rotation angles from matrix */
|
|
/* =================================== */
|
|
|
|
void
|
|
ArcBall_Values (double *alpha,
|
|
double *beta,
|
|
double *gamma)
|
|
{
|
|
if ((*beta=asin(-mNow[0][2]))!=0.0)
|
|
{
|
|
*gamma=atan2(mNow[1][2],mNow[2][2]);
|
|
*alpha=atan2(mNow[0][1],mNow[0][0]);
|
|
}
|
|
else
|
|
{
|
|
*gamma=atan2(mNow[1][0],mNow[1][1]);
|
|
*alpha=0.0;
|
|
}
|
|
}
|
|
|
|
/* Begin drag sequence. */
|
|
/* ==================== */
|
|
|
|
void
|
|
ArcBall_BeginDrag (void)
|
|
{
|
|
dragging = TRUE;
|
|
vDown = vNow;
|
|
}
|
|
|
|
/* Stop drag sequence. */
|
|
/* =================== */
|
|
|
|
void
|
|
ArcBall_EndDrag (void)
|
|
{
|
|
dragging = FALSE;
|
|
qDown = qNow;
|
|
|
|
ArcBall_CopyMat (mNow, mDown);
|
|
}
|
|
|
|
/*===================*/
|
|
/***** BallAux.c *****/
|
|
/*===================*/
|
|
|
|
/* Return quaternion product qL * qR. Note: order is important! */
|
|
/* To combine rotations, use the product Mul(qSecond, qFirst), */
|
|
/* which gives the effect of rotating by qFirst then qSecond. */
|
|
/* ============================================================= */
|
|
|
|
static Quat
|
|
Qt_Mul (Quat qL,
|
|
Quat qR)
|
|
{
|
|
Quat qq;
|
|
qq.w = qL.w*qR.w - qL.x*qR.x - qL.y*qR.y - qL.z*qR.z;
|
|
qq.x = qL.w*qR.x + qL.x*qR.w + qL.y*qR.z - qL.z*qR.y;
|
|
qq.y = qL.w*qR.y + qL.y*qR.w + qL.z*qR.x - qL.x*qR.z;
|
|
qq.z = qL.w*qR.z + qL.z*qR.w + qL.x*qR.y - qL.y*qR.x;
|
|
return (qq);
|
|
}
|
|
|
|
/* Construct rotation matrix from (possibly non-unit) quaternion. */
|
|
/* Assumes matrix is used to multiply column vector on the left: */
|
|
/* vnew = mat vold. Works correctly for right-handed coordinate */
|
|
/* system and right-handed rotations. */
|
|
/* ============================================================== */
|
|
|
|
static void
|
|
Qt_ToMatrix (Quat q,
|
|
HMatrix out)
|
|
{
|
|
double Nq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w;
|
|
double s = (Nq > 0.0) ? (2.0 / Nq) : 0.0;
|
|
double xs = q.x*s, ys = q.y*s, zs = q.z*s;
|
|
double wx = q.w*xs, wy = q.w*ys, wz = q.w*zs;
|
|
double xx = q.x*xs, xy = q.x*ys, xz = q.x*zs;
|
|
double yy = q.y*ys, yz = q.y*zs, zz = q.z*zs;
|
|
out[X][X] = 1.0 - (yy + zz); out[Y][X] = xy + wz; out[Z][X] = xz - wy;
|
|
out[X][Y] = xy - wz; out[Y][Y] = 1.0 - (xx + zz); out[Z][Y] = yz + wx;
|
|
out[X][Z] = xz + wy; out[Y][Z] = yz - wx; out[Z][Z] = 1.0 - (xx + yy);
|
|
out[X][W] = out[Y][W] = out[Z][W] = out[W][X] = out[W][Y] = out[W][Z] = 0.0;
|
|
out[W][W] = 1.0;
|
|
}
|
|
|
|
/* Return conjugate of quaternion. */
|
|
/* =============================== */
|
|
|
|
static Quat
|
|
Qt_Conj (Quat q)
|
|
{
|
|
Quat qq;
|
|
qq.x = -q.x; qq.y = -q.y; qq.z = -q.z; qq.w = q.w;
|
|
return (qq);
|
|
}
|
|
|
|
/* Return vector formed from components */
|
|
/* ==================================== */
|
|
|
|
static HVect
|
|
V3_ (double x,
|
|
double y,
|
|
double z)
|
|
{
|
|
HVect v;
|
|
v.x = x; v.y = y; v.z = z; v.w = 0;
|
|
return (v);
|
|
}
|
|
|
|
/* Return norm of v, defined as sum of squares of components */
|
|
/* ========================================================= */
|
|
|
|
static double
|
|
V3_Norm (HVect v)
|
|
{
|
|
return ( v.x*v.x + v.y*v.y + v.z*v.z );
|
|
}
|
|
|
|
/* Return unit magnitude vector in direction of v */
|
|
/* ============================================== */
|
|
|
|
static HVect
|
|
V3_Unit (HVect v)
|
|
{
|
|
static HVect u = {0, 0, 0, 0};
|
|
double vlen = sqrt(V3_Norm(v));
|
|
|
|
if (vlen != 0.0) u.x = v.x/vlen; u.y = v.y/vlen; u.z = v.z/vlen;
|
|
return (u);
|
|
}
|
|
|
|
/* Return version of v scaled by s */
|
|
/* =============================== */
|
|
|
|
static HVect
|
|
V3_Scale (HVect v,
|
|
double s)
|
|
{
|
|
HVect u;
|
|
u.x = s*v.x; u.y = s*v.y; u.z = s*v.z; u.w = v.w;
|
|
return (u);
|
|
}
|
|
|
|
/* Return negative of v */
|
|
/* ==================== */
|
|
|
|
static HVect
|
|
V3_Negate (HVect v)
|
|
{
|
|
static HVect u = {0, 0, 0, 0};
|
|
u.x = -v.x; u.y = -v.y; u.z = -v.z;
|
|
return (u);
|
|
}
|
|
|
|
/* Return sum of v1 and v2 */
|
|
/* ======================= */
|
|
/*
|
|
static HVect
|
|
V3_Add (HVect v1,
|
|
HVect v2)
|
|
{
|
|
static HVect v = {0, 0, 0, 0};
|
|
v.x = v1.x+v2.x; v.y = v1.y+v2.y; v.z = v1.z+v2.z;
|
|
return (v);
|
|
}
|
|
*/
|
|
/* Return difference of v1 minus v2 */
|
|
/* ================================ */
|
|
|
|
static HVect
|
|
V3_Sub (HVect v1,
|
|
HVect v2)
|
|
{
|
|
static HVect v = {0, 0, 0, 0};
|
|
v.x = v1.x-v2.x; v.y = v1.y-v2.y; v.z = v1.z-v2.z;
|
|
return (v);
|
|
}
|
|
|
|
/* Halve arc between unit vectors v0 and v1. */
|
|
/* ========================================= */
|
|
/*
|
|
static HVect
|
|
V3_Bisect (HVect v0,
|
|
HVect v1)
|
|
{
|
|
HVect v = {0, 0, 0, 0};
|
|
double Nv;
|
|
|
|
v = V3_Add(v0, v1);
|
|
Nv = V3_Norm(v);
|
|
if (Nv < 1.0e-5) v = V3_(0, 0, 1);
|
|
else v = V3_Scale(v, 1/sqrt(Nv));
|
|
return (v);
|
|
}
|
|
*/
|
|
|
|
/* Return dot product of v1 and v2 */
|
|
/* =============================== */
|
|
|
|
static double
|
|
V3_Dot (HVect v1,
|
|
HVect v2)
|
|
{
|
|
return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
|
|
}
|
|
|
|
|
|
/* Return cross product, v1 x v2 */
|
|
/* ============================= */
|
|
/*
|
|
static HVect
|
|
V3_Cross (HVect v1,
|
|
HVect v2)
|
|
{
|
|
static HVect v = {0, 0, 0, 0};
|
|
v.x = v1.y*v2.z-v1.z*v2.y;
|
|
v.y = v1.z*v2.x-v1.x*v2.z;
|
|
v.z = v1.x*v2.y-v1.y*v2.x;
|
|
return (v);
|
|
}
|
|
*/
|
|
|
|
void
|
|
ArcBall_CopyMat (HMatrix inm,
|
|
HMatrix outm)
|
|
{
|
|
int x=0,y=0;
|
|
|
|
for (x=0;x<4;x++)
|
|
{
|
|
for (y=0;y<4;y++)
|
|
{
|
|
outm[y][x]=inm[y][x];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*=====================================================*/
|
|
/**** BallMath.c - Essential routines for ArcBall. ****/
|
|
/*=====================================================*/
|
|
|
|
/* Convert window coordinates to sphere coordinates. */
|
|
/* ================================================= */
|
|
|
|
static HVect
|
|
MouseOnSphere (HVect mouse,
|
|
HVect ballCenter,
|
|
double ballRadius)
|
|
{
|
|
HVect ballMouse;
|
|
register double mag;
|
|
|
|
ballMouse.x = (mouse.x - ballCenter.x) / ballRadius;
|
|
ballMouse.y = (mouse.y - ballCenter.y) / ballRadius;
|
|
mag = ballMouse.x*ballMouse.x + ballMouse.y*ballMouse.y;
|
|
if (mag > 1.0)
|
|
{
|
|
register double scale = 1.0/sqrt(mag);
|
|
ballMouse.x *= scale; ballMouse.y *= scale;
|
|
ballMouse.z = 0.0;
|
|
}
|
|
else ballMouse.z = sqrt(1 - mag);
|
|
ballMouse.w = 0.0;
|
|
return (ballMouse);
|
|
}
|
|
|
|
/* Construct a unit quaternion from two points on unit sphere */
|
|
/* ========================================================== */
|
|
|
|
static Quat
|
|
Qt_FromBallPoints (HVect from,
|
|
HVect to)
|
|
{
|
|
Quat qu;
|
|
qu.x = from.y*to.z - from.z*to.y;
|
|
qu.y = from.z*to.x - from.x*to.z;
|
|
qu.z = from.x*to.y - from.y*to.x;
|
|
qu.w = from.x*to.x + from.y*to.y + from.z*to.z;
|
|
return (qu);
|
|
}
|
|
|
|
/* Convert a unit quaternion to two points on unit sphere */
|
|
/* ====================================================== */
|
|
|
|
static void
|
|
Qt_ToBallPoints (Quat q,
|
|
HVect *arcFrom,
|
|
HVect *arcTo)
|
|
{
|
|
double s;
|
|
|
|
s = sqrt(q.x*q.x + q.y*q.y);
|
|
if (s == 0.0) *arcFrom = V3_(0.0, 1.0, 0.0);
|
|
else *arcFrom = V3_(-q.y/s, q.x/s, 0.0);
|
|
arcTo->x = q.w*arcFrom->x - q.z*arcFrom->y;
|
|
arcTo->y = q.w*arcFrom->y + q.z*arcFrom->x;
|
|
arcTo->z = q.x*arcFrom->y - q.y*arcFrom->x;
|
|
if (q.w < 0.0) *arcFrom = V3_(-arcFrom->x, -arcFrom->y, 0.0);
|
|
}
|
|
|
|
/* Force sphere point to be perpendicular to axis. */
|
|
/* =============================================== */
|
|
|
|
static HVect
|
|
ConstrainToAxis (HVect loose,
|
|
HVect axis)
|
|
{
|
|
HVect onPlane;
|
|
register double norm;
|
|
|
|
onPlane = V3_Sub(loose, V3_Scale(axis, V3_Dot(axis, loose)));
|
|
norm = V3_Norm(onPlane);
|
|
if (norm > 0.0)
|
|
{
|
|
if (onPlane.z < 0.0) onPlane = V3_Negate(onPlane);
|
|
return ( V3_Scale(onPlane, 1/sqrt(norm)) );
|
|
}
|
|
/* else drop through */
|
|
/* ================= */
|
|
|
|
if (axis.z == 1) onPlane = V3_(1.0, 0.0, 0.0);
|
|
else onPlane = V3_Unit(V3_(-axis.y, axis.x, 0.0));
|
|
return (onPlane);
|
|
}
|
|
|
|
/* Find the index of nearest arc of axis set. */
|
|
/* ========================================== */
|
|
|
|
static int
|
|
NearestConstraintAxis (HVect loose,
|
|
HVect *axes,
|
|
int nAxes)
|
|
{
|
|
HVect onPlane;
|
|
register double max, dot;
|
|
register int i, nearest;
|
|
max = -1; nearest = 0;
|
|
|
|
for (i=0; i<nAxes; i++)
|
|
{
|
|
onPlane = ConstrainToAxis(loose, axes[i]);
|
|
dot = V3_Dot(onPlane, loose);
|
|
if (dot>max)
|
|
{
|
|
max = dot; nearest = i;
|
|
}
|
|
}
|
|
return (nearest);
|
|
}
|