xref: /plan9/sys/src/libgeometry/quaternion.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
1*219b2ee8SDavid du Colombier /*
2*219b2ee8SDavid du Colombier  * Quaternion arithmetic:
3*219b2ee8SDavid du Colombier  *	qadd(q, r)	returns q+r
4*219b2ee8SDavid du Colombier  *	qsub(q, r)	returns q-r
5*219b2ee8SDavid du Colombier  *	qneg(q)		returns -q
6*219b2ee8SDavid du Colombier  *	qmul(q, r)	returns q*r
7*219b2ee8SDavid du Colombier  *	qdiv(q, r)	returns q/r, can divide check.
8*219b2ee8SDavid du Colombier  *	qinv(q)		returns 1/q, can divide check.
9*219b2ee8SDavid du Colombier  *	double qlen(p)	returns modulus of p
10*219b2ee8SDavid du Colombier  *	qunit(q)	returns a unit quaternion parallel to q
11*219b2ee8SDavid du Colombier  * The following only work on unit quaternions and rotation matrices:
12*219b2ee8SDavid du Colombier  *	slerp(q, r, a)	returns q*(r*q^-1)^a
13*219b2ee8SDavid du Colombier  *	qmid(q, r)	slerp(q, r, .5)
14*219b2ee8SDavid du Colombier  *	qsqrt(q)	qmid(q, (Quaternion){1,0,0,0})
15*219b2ee8SDavid du Colombier  *	qtom(m, q)	converts a unit quaternion q into a rotation matrix m
16*219b2ee8SDavid du Colombier  *	mtoq(m)		returns a quaternion equivalent to a rotation matrix m
17*219b2ee8SDavid du Colombier  */
18*219b2ee8SDavid du Colombier #include <u.h>
19*219b2ee8SDavid du Colombier #include <libc.h>
20*219b2ee8SDavid du Colombier #include <libg.h>
21*219b2ee8SDavid du Colombier #include <geometry.h>
22*219b2ee8SDavid du Colombier void qtom(Matrix m, Quaternion q){
23*219b2ee8SDavid du Colombier #ifndef new
24*219b2ee8SDavid du Colombier 	m[0][0]=1-2*(q.j*q.j+q.k*q.k);
25*219b2ee8SDavid du Colombier 	m[0][1]=2*(q.i*q.j+q.r*q.k);
26*219b2ee8SDavid du Colombier 	m[0][2]=2*(q.i*q.k-q.r*q.j);
27*219b2ee8SDavid du Colombier 	m[0][3]=0;
28*219b2ee8SDavid du Colombier 	m[1][0]=2*(q.i*q.j-q.r*q.k);
29*219b2ee8SDavid du Colombier 	m[1][1]=1-2*(q.i*q.i+q.k*q.k);
30*219b2ee8SDavid du Colombier 	m[1][2]=2*(q.j*q.k+q.r*q.i);
31*219b2ee8SDavid du Colombier 	m[1][3]=0;
32*219b2ee8SDavid du Colombier 	m[2][0]=2*(q.i*q.k+q.r*q.j);
33*219b2ee8SDavid du Colombier 	m[2][1]=2*(q.j*q.k-q.r*q.i);
34*219b2ee8SDavid du Colombier 	m[2][2]=1-2*(q.i*q.i+q.j*q.j);
35*219b2ee8SDavid du Colombier 	m[2][3]=0;
36*219b2ee8SDavid du Colombier 	m[3][0]=0;
37*219b2ee8SDavid du Colombier 	m[3][1]=0;
38*219b2ee8SDavid du Colombier 	m[3][2]=0;
39*219b2ee8SDavid du Colombier 	m[3][3]=1;
40*219b2ee8SDavid du Colombier #else
41*219b2ee8SDavid du Colombier 	/*
42*219b2ee8SDavid du Colombier 	 * Transcribed from Ken Shoemake's new code -- not known to work
43*219b2ee8SDavid du Colombier 	 */
44*219b2ee8SDavid du Colombier 	double Nq = q.r*q.r+q.i*q.i+q.j*q.j+q.k*q.k;
45*219b2ee8SDavid du Colombier 	double s = (Nq > 0.0) ? (2.0 / Nq) : 0.0;
46*219b2ee8SDavid du Colombier 	double xs = q.i*s,		ys = q.j*s,		zs = q.k*s;
47*219b2ee8SDavid du Colombier 	double wx = q.r*xs,		wy = q.r*ys,		wz = q.r*zs;
48*219b2ee8SDavid du Colombier 	double xx = q.i*xs,		xy = q.i*ys,		xz = q.i*zs;
49*219b2ee8SDavid du Colombier 	double yy = q.j*ys,		yz = q.j*zs,		zz = q.k*zs;
50*219b2ee8SDavid du Colombier 	m[0][0] = 1.0 - (yy + zz); m[1][0] = xy + wz;         m[2][0] = xz - wy;
51*219b2ee8SDavid du Colombier 	m[0][1] = xy - wz;         m[1][1] = 1.0 - (xx + zz); m[2][1] = yz + wx;
52*219b2ee8SDavid du Colombier 	m[0][2] = xz + wy;         m[1][2] = yz - wx;         m[2][2] = 1.0 - (xx + yy);
53*219b2ee8SDavid du Colombier 	m[0][3] = m[1][3] = m[2][3] = m[3][0] = m[3][1] = m[3][2] = 0.0;
54*219b2ee8SDavid du Colombier 	m[3][3] = 1.0;
55*219b2ee8SDavid du Colombier #endif
56*219b2ee8SDavid du Colombier }
57*219b2ee8SDavid du Colombier Quaternion mtoq(Matrix mat){
58*219b2ee8SDavid du Colombier #ifndef new
59*219b2ee8SDavid du Colombier #define	EPS	1.387778780781445675529539585113525e-17	/* 2^-56 */
60*219b2ee8SDavid du Colombier 	double t;
61*219b2ee8SDavid du Colombier 	Quaternion q=(Quaternion){0.,0.,0.,1.};
62*219b2ee8SDavid du Colombier 	if((t=.25*(1+mat[0][0]+mat[1][1]+mat[2][2]))>EPS){
63*219b2ee8SDavid du Colombier 		q.r=sqrt(t);
64*219b2ee8SDavid du Colombier 		t=4*q.r;
65*219b2ee8SDavid du Colombier 		q.i=(mat[1][2]-mat[2][1])/t;
66*219b2ee8SDavid du Colombier 		q.j=(mat[2][0]-mat[0][2])/t;
67*219b2ee8SDavid du Colombier 		q.k=(mat[0][1]-mat[1][0])/t;
68*219b2ee8SDavid du Colombier 	}
69*219b2ee8SDavid du Colombier 	else if((t=-.5*(mat[1][1]+mat[2][2]))>EPS){
70*219b2ee8SDavid du Colombier 		q.i=sqrt(t);
71*219b2ee8SDavid du Colombier 		t=2*q.i;
72*219b2ee8SDavid du Colombier 		q.j=mat[0][1]/t;
73*219b2ee8SDavid du Colombier 		q.k=mat[0][2]/t;
74*219b2ee8SDavid du Colombier 	}
75*219b2ee8SDavid du Colombier 	else if((t=.5*(1-mat[2][2]))>EPS){
76*219b2ee8SDavid du Colombier 		q.j=sqrt(t);
77*219b2ee8SDavid du Colombier 		q.k=mat[1][2]/(2*q.j);
78*219b2ee8SDavid du Colombier 	}
79*219b2ee8SDavid du Colombier 	return q;
80*219b2ee8SDavid du Colombier #else
81*219b2ee8SDavid du Colombier 	/*
82*219b2ee8SDavid du Colombier 	 * Transcribed from Ken Shoemake's new code -- not known to work
83*219b2ee8SDavid du Colombier 	 */
84*219b2ee8SDavid du Colombier 	/* This algorithm avoids near-zero divides by looking for a large
85*219b2ee8SDavid du Colombier 	 * component -- first r, then i, j, or k.  When the trace is greater than zero,
86*219b2ee8SDavid du Colombier 	 * |r| is greater than 1/2, which is as small as a largest component can be.
87*219b2ee8SDavid du Colombier 	 * Otherwise, the largest diagonal entry corresponds to the largest of |i|,
88*219b2ee8SDavid du Colombier 	 * |j|, or |k|, one of which must be larger than |r|, and at least 1/2.
89*219b2ee8SDavid du Colombier 	 */
90*219b2ee8SDavid du Colombier 	Quaternion qu;
91*219b2ee8SDavid du Colombier 	double tr, s;
92*219b2ee8SDavid du Colombier 
93*219b2ee8SDavid du Colombier 	tr = mat[0][0] + mat[1][1] + mat[2][2];
94*219b2ee8SDavid du Colombier 	if (tr >= 0.0) {
95*219b2ee8SDavid du Colombier 		s = sqrt(tr + mat[3][3]);
96*219b2ee8SDavid du Colombier 		qu.r = s*0.5;
97*219b2ee8SDavid du Colombier 		s = 0.5 / s;
98*219b2ee8SDavid du Colombier 		qu.i = (mat[2][1] - mat[1][2]) * s;
99*219b2ee8SDavid du Colombier 		qu.j = (mat[0][2] - mat[2][0]) * s;
100*219b2ee8SDavid du Colombier 		qu.k = (mat[1][0] - mat[0][1]) * s;
101*219b2ee8SDavid du Colombier 	}
102*219b2ee8SDavid du Colombier 	else {
103*219b2ee8SDavid du Colombier 		int i = 0;
104*219b2ee8SDavid du Colombier 		if (mat[1][1] > mat[0][0]) i = 1;
105*219b2ee8SDavid du Colombier 		if (mat[2][2] > mat[i][i]) i = 2;
106*219b2ee8SDavid du Colombier 		switch(i){
107*219b2ee8SDavid du Colombier 		case 0:
108*219b2ee8SDavid du Colombier 			s = sqrt( (mat[0][0] - (mat[1][1]+mat[2][2])) + mat[3][3] );
109*219b2ee8SDavid du Colombier 			qu.i = s*0.5;
110*219b2ee8SDavid du Colombier 			s = 0.5 / s;
111*219b2ee8SDavid du Colombier 			qu.j = (mat[0][1] + mat[1][0]) * s;
112*219b2ee8SDavid du Colombier 			qu.k = (mat[2][0] + mat[0][2]) * s;
113*219b2ee8SDavid du Colombier 			qu.r = (mat[2][1] - mat[1][2]) * s;
114*219b2ee8SDavid du Colombier 			break;
115*219b2ee8SDavid du Colombier 		case 1:
116*219b2ee8SDavid du Colombier 			s = sqrt( (mat[1][1] - (mat[2][2]+mat[0][0])) + mat[3][3] );
117*219b2ee8SDavid du Colombier 			qu.j = s*0.5;
118*219b2ee8SDavid du Colombier 			s = 0.5 / s;
119*219b2ee8SDavid du Colombier 			qu.k = (mat[1][2] + mat[2][1]) * s;
120*219b2ee8SDavid du Colombier 			qu.i = (mat[0][1] + mat[1][0]) * s;
121*219b2ee8SDavid du Colombier 			qu.r = (mat[0][2] - mat[2][0]) * s;
122*219b2ee8SDavid du Colombier 			break;
123*219b2ee8SDavid du Colombier 		case 2:
124*219b2ee8SDavid du Colombier 			s = sqrt( (mat[2][2] - (mat[0][0]+mat[1][1])) + mat[3][3] );
125*219b2ee8SDavid du Colombier 			qu.k = s*0.5;
126*219b2ee8SDavid du Colombier 			s = 0.5 / s;
127*219b2ee8SDavid du Colombier 			qu.i = (mat[2][0] + mat[0][2]) * s;
128*219b2ee8SDavid du Colombier 			qu.j = (mat[1][2] + mat[2][1]) * s;
129*219b2ee8SDavid du Colombier 			qu.r = (mat[1][0] - mat[0][1]) * s;
130*219b2ee8SDavid du Colombier 			break;
131*219b2ee8SDavid du Colombier 		}
132*219b2ee8SDavid du Colombier 	}
133*219b2ee8SDavid du Colombier 	if (mat[3][3] != 1.0){
134*219b2ee8SDavid du Colombier 		s=1/sqrt(mat[3][3]);
135*219b2ee8SDavid du Colombier 		qu.r*=s;
136*219b2ee8SDavid du Colombier 		qu.i*=s;
137*219b2ee8SDavid du Colombier 		qu.j*=s;
138*219b2ee8SDavid du Colombier 		qu.k*=s;
139*219b2ee8SDavid du Colombier 	}
140*219b2ee8SDavid du Colombier 	return (qu);
141*219b2ee8SDavid du Colombier #endif
142*219b2ee8SDavid du Colombier }
143*219b2ee8SDavid du Colombier Quaternion qadd(Quaternion q, Quaternion r){
144*219b2ee8SDavid du Colombier 	return (Quaternion){q.r+r.r, q.i+r.i, q.j+r.j, q.k+r.k};
145*219b2ee8SDavid du Colombier }
146*219b2ee8SDavid du Colombier Quaternion qsub(Quaternion q, Quaternion r){
147*219b2ee8SDavid du Colombier 	return (Quaternion){q.r-r.r, q.i-r.i, q.j-r.j, q.k-r.k};
148*219b2ee8SDavid du Colombier }
149*219b2ee8SDavid du Colombier Quaternion qneg(Quaternion q){
150*219b2ee8SDavid du Colombier 	return (Quaternion){-q.r, -q.i, -q.j, -q.k};
151*219b2ee8SDavid du Colombier }
152*219b2ee8SDavid du Colombier Quaternion qmul(Quaternion q, Quaternion r){
153*219b2ee8SDavid du Colombier 	return (Quaternion){q.r*r.r-q.i*r.i-q.j*r.j-q.k*r.k,
154*219b2ee8SDavid du Colombier 			   q.r*r.i+r.r*q.i+q.j*r.k-q.k*r.j,
155*219b2ee8SDavid du Colombier 			   q.r*r.j+r.r*q.j+q.k*r.i-q.i*r.k,
156*219b2ee8SDavid du Colombier 			   q.r*r.k+r.r*q.k+q.i*r.j-q.j*r.i};
157*219b2ee8SDavid du Colombier }
158*219b2ee8SDavid du Colombier Quaternion qdiv(Quaternion q, Quaternion r){
159*219b2ee8SDavid du Colombier 	return qmul(q, qinv(r));
160*219b2ee8SDavid du Colombier }
161*219b2ee8SDavid du Colombier Quaternion qunit(Quaternion q){
162*219b2ee8SDavid du Colombier 	double l=qlen(q);
163*219b2ee8SDavid du Colombier 	return (Quaternion){q.r/l, q.i/l, q.j/l, q.k/l};
164*219b2ee8SDavid du Colombier }
165*219b2ee8SDavid du Colombier /*
166*219b2ee8SDavid du Colombier  * Bug?: takes no action on divide check
167*219b2ee8SDavid du Colombier  */
168*219b2ee8SDavid du Colombier Quaternion qinv(Quaternion q){
169*219b2ee8SDavid du Colombier 	double l=q.r*q.r+q.i*q.i+q.j*q.j+q.k*q.k;
170*219b2ee8SDavid du Colombier 	return (Quaternion){q.r/l, -q.i/l, -q.j/l, -q.k/l};
171*219b2ee8SDavid du Colombier }
172*219b2ee8SDavid du Colombier double qlen(Quaternion p){
173*219b2ee8SDavid du Colombier 	return sqrt(p.r*p.r+p.i*p.i+p.j*p.j+p.k*p.k);
174*219b2ee8SDavid du Colombier }
175*219b2ee8SDavid du Colombier Quaternion slerp(Quaternion q, Quaternion r, double a){
176*219b2ee8SDavid du Colombier 	double u, v, ang, s;
177*219b2ee8SDavid du Colombier 	double dot=q.r*r.r+q.i*r.i+q.j*r.j+q.k*r.k;
178*219b2ee8SDavid du Colombier 	ang=dot<-1?PI:dot>1?0:acos(dot); /* acos gives NaN for dot slightly out of range */
179*219b2ee8SDavid du Colombier 	s=sin(ang);
180*219b2ee8SDavid du Colombier 	if(s==0) return ang<PI/2?q:r;
181*219b2ee8SDavid du Colombier 	u=sin((1-a)*ang)/s;
182*219b2ee8SDavid du Colombier 	v=sin(a*ang)/s;
183*219b2ee8SDavid du Colombier 	return (Quaternion){u*q.r+v*r.r, u*q.i+v*r.i, u*q.j+v*r.j, u*q.k+v*r.k};
184*219b2ee8SDavid du Colombier }
185*219b2ee8SDavid du Colombier /*
186*219b2ee8SDavid du Colombier  * Only works if qlen(q)==qlen(r)==1
187*219b2ee8SDavid du Colombier  */
188*219b2ee8SDavid du Colombier Quaternion qmid(Quaternion q, Quaternion r){
189*219b2ee8SDavid du Colombier 	double l;
190*219b2ee8SDavid du Colombier 	q=qadd(q, r);
191*219b2ee8SDavid du Colombier 	l=qlen(q);
192*219b2ee8SDavid du Colombier 	if(l<1e-12) return (Quaternion){r.i,-r.r,r.k,-r.j};
193*219b2ee8SDavid du Colombier 	return (Quaternion){q.r/l, q.i/l, q.j/l, q.k/l};
194*219b2ee8SDavid du Colombier }
195*219b2ee8SDavid du Colombier /*
196*219b2ee8SDavid du Colombier  * Only works if qlen(q)==1
197*219b2ee8SDavid du Colombier  */
198*219b2ee8SDavid du Colombier Quaternion qsqrt(Quaternion q){
199*219b2ee8SDavid du Colombier 	return qmid(q, (Quaternion){1,0,0,0});
200*219b2ee8SDavid du Colombier }
201