OSDN Git Service

Batch mode is implemented (still testing)
[molby/Molby.git] / MolLib / Trackball.c
1 /*
2  *  Trackball.c
3  *
4  *  Created by Toshi Nagata on 2005/09/17.
5  *  Copyright 2005-2008 Toshi Nagata. All rights reserved.
6  *
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation version 2 of the License.
10  
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15  */
16
17 #include "Trackball.h"
18 #include "Types.h"
19 #include <string.h>
20
21 #pragma mark ====== Internal functions ======
22
23 static void
24 multiplyQuat(const double *a, const double *b, double *c)
25 {
26     c[0] = a[0] * b[0] - (a[1] * b[1] + a[2] * b[2] + a[3] * b[3]);
27     c[1] = b[0] * a[1] + a[0] * b[1] + (a[2] * b[3] - a[3] * b[2]);
28     c[2] = b[0] * a[2] + a[0] * b[2] + (a[3] * b[1] - a[1] * b[3]);
29     c[3] = b[0] * a[3] + a[0] * b[3] + (a[1] * b[2] - a[2] * b[1]);
30 }
31
32 static void
33 normalizeQuat(double *a)
34 {
35     float f;
36     f = 1.0 / sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2] + a[3] * a[3]);
37     a[0] *= f;
38     a[1] *= f;
39     a[2] *= f;
40     a[3] *= f;
41 }
42
43 static void
44 getRotationToCenter(double x, double y, double radius, double *quat)
45 {
46     const float sqrt2 = 1.41421356 / 2;
47     float r, r2;
48     r2 = radius * radius;
49     r = x * x + y * y;
50     if (r >= r2) {
51         /*  Rotation of 90 degree along (y, -x, 0) */
52         r = 1.0 / sqrt(r);
53         x *= r;
54         y *= r;
55         quat[0] = sqrt2;
56         quat[1] = sqrt2 * y;
57         quat[2] = -sqrt2 * x;
58         quat[3] = 0.0;
59     } else {
60         float z, cosAng, sinAng;
61         z = sqrt(r2 - r);       /* z coordinate of the point on the sphere */
62         /*  Rotation of arccos(z/radius) along (y, -x, 0)  */
63         if (r < 1e-6) {
64             quat[0] = 1.0;
65             quat[1] = quat[2] = quat[3] = 0.0;
66         } else {
67             cosAng = sqrt(0.5 * (1.0 + z / radius));
68             sinAng = sqrt(0.5 * (1.0 - z / radius));
69             quat[0] = cosAng;
70             r = 1.0 / sqrt(r);
71             quat[1] = sinAng * y * r;
72             quat[2] = -sinAng * x * r;
73             quat[3] = 0.0;
74         }
75     }
76 }
77
78 static void
79 rotation2Quat(const double *A, double *q)
80 {
81     float ang2;  /* The half-angle */
82     float sinAng2; /* sin(half-angle) */
83     
84     /* Convert a GL-style rotation to a quaternion.  The GL rotation looks like this:
85      * {angle, x, y, z}, the corresponding quaternion looks like this:
86      * {{v}, cos(angle/2)}, where {v} is {x, y, z} / sin(angle/2).  */
87     
88     ang2 = A[0] * kDeg2Rad * 0.5;  /* Convert from degrees to radians, get the half-angle. */
89     sinAng2 = sin(ang2);
90     q[0] = A[1] * sinAng2; q[1] = A[2] * sinAng2; q[2] = A[3] * sinAng2;
91     q[3] = cos(ang2);
92 }
93
94 #pragma mark ====== New/Retain/Release ======
95
96 Trackball *
97 TrackballNew(void)
98 {
99         Trackball *track = (Trackball *)calloc(sizeof(Trackball), 1);
100         MALLOC_CHECK(track, "allocating a trackball record");
101     track->quat[0] = track->tempQuat[0] = 1.0;
102         return track;
103 }
104
105 void
106 TrackballRetain(Trackball *track)
107 {
108         if (track != NULL)
109                 track->refCount++;
110 }
111
112 void
113 TrackballRelease(Trackball *track)
114 {
115         if (track != NULL && --(track->refCount) == 0) {
116                 free(track);
117         }
118 }
119
120 float
121 TrackballGetScale(const Trackball *track)
122 {
123         NULL_CHECK(track, "TrackballGetScale"); 
124     return track->tempScale + track->scale;
125 }
126
127 /*  Get the current rotation (in GL format)  */
128 void
129 TrackballGetRotate(const Trackball *track, double *a)
130 {
131     double w[4], t, f;
132         NULL_CHECK(track, "TrackballGetRotate");        
133     /*  Rotate: multiply the two quaternions and convert it to a GL rotater  */
134     multiplyQuat(track->tempQuat, track->quat, w);
135     normalizeQuat(w);
136     if (fabs(fabs(w[0]) - 1.0) < 1e-7) {
137         /*  Identity rotation  */
138         a[0] = 0.0;
139         a[1] = 1.0;
140         a[2] = a[3] = 0.0;
141     } else {
142         /*  Quaternion: (cos(theta/2), sin(theta/2)*(x,y,z))  */
143         /*  GL rotater: (theta, x, y, z)  */
144         t = acos(w[0]);
145         a[0] = t * 2.0 * kRad2Deg;
146         f = 1.0 / sin(t);
147         a[1] = w[1] * f;
148         a[2] = w[2] * f;
149         a[3] = w[3] * f;
150     }
151 }
152
153 /*  Get the current translation.  The result values should be multiplied with
154  * the dimension of the model.  */
155 void
156 TrackballGetTranslate(const Trackball *track, double *a)
157 {
158         NULL_CHECK(track, "TrackballGetTranslate");
159     a[0] = track->tempTrans[0] + track->trans[0];
160     a[1] = track->tempTrans[1] + track->trans[1];
161     a[2] = track->tempTrans[2] + track->trans[2];
162 }
163
164 /*  Get the perspective parameter (fovy and distance)
165  *  Fovy (a[0]) is in degree.
166  *  Distance (a[1]) should be multiplied with the dimension of the model. */
167 void
168 TrackballGetPerspective(const Trackball *track, double *a)
169 {
170     double f;
171         NULL_CHECK(track, "TrackballGetPerspective");
172     /*  Get the current scale factor (-1.0 to 1.0 for x1/10 to x10)  */
173     f = track->tempScale + track->scale;
174     if (f < 0) {
175         /*  Shrink: fovy is fixed at 30 degree, and distance is enlarged  */
176         if (f < -5.0)
177             f = -5.0;
178         a[1] = kCot15Deg * exp(-kLog10 * f);
179         a[0] = 30.0;
180     } else {
181         /*  Expand: fovy is reduced while distance fixed at kCot15Deg  */
182         if (f > 5.0)
183             f = 5.0;
184         a[0] = 30.0 * exp(-kLog10 * f);
185         a[1] = kCot15Deg;
186     }
187 }
188
189 int
190 TrackballGetModifyCount(Trackball *track)
191 {
192         return (track != NULL ? track->modifyCount : 0);
193 }
194
195 void
196 TrackballReset(Trackball *track)
197 {
198         NULL_CHECK(track, "TrackballReset");
199         memset(track->trans, 0, sizeof(track->trans));
200         memset(track->tempTrans, 0, sizeof(track->trans));
201         memset(track->quat, 0, sizeof(track->trans));
202         memset(track->tempQuat, 0, sizeof(track->trans));
203     track->quat[0] = track->tempQuat[0] = 1.0;
204         track->tempScale = 0;
205         track->scale = 0;
206         track->modifyCount++;
207 }
208
209 void
210 TrackballSetScale(Trackball *track, double scale)
211 {
212         NULL_CHECK(track, "TrackballSetScale");
213         track->tempScale = 0;
214         track->scale = scale;
215         track->modifyCount++;
216 }
217
218 void
219 TrackballSetRotate(Trackball *track, const double *a)
220 {
221         double k; 
222         NULL_CHECK(track, "TrackballSetRotate");
223         track->tempQuat[0] = 1.0;
224         track->tempQuat[1] = track->tempQuat[2] = track->tempQuat[3] = 0.0;
225         track->quat[0] = cos(a[0] * 0.5 * kDeg2Rad);
226         k = sin(a[0] * 0.5 * kDeg2Rad);
227         track->quat[1] = k * a[1];
228         track->quat[2] = k * a[2];
229         track->quat[3] = k * a[3];
230         track->modifyCount++;
231 }
232
233 void
234 TrackballSetTranslate(Trackball *track, const double *a)
235 {
236         NULL_CHECK(track, "TrackballSetTranslate");
237         track->tempTrans[0] = track->tempTrans[1] = track->tempTrans[2] = 0;
238         track->trans[0] = a[0];
239         track->trans[1] = a[1];
240         track->trans[2] = a[2];
241         track->modifyCount++;
242 }
243
244 #pragma mark ====== Mouse operations ======
245
246 void
247 TrackballStartDragging(Trackball *track, const float *mousePos, TrackballMode mode)
248 {
249         NULL_CHECK(track, "TrackballStartDragging");
250     
251     /*  1: rotate, 2: translate, 3: scale  */
252     track->mode = mode;
253
254     /*  The start point vector  */
255         if (mousePos != NULL) {
256                 track->start[0] = mousePos[0];
257                 track->start[1] = mousePos[1];
258         } else {
259                 track->start[0] = track->start[1] = 0.0;
260         }
261     
262     /*  The rotation from the start point to the center, for trackball operation  */
263     getRotationToCenter(track->start[0], track->start[1], 1.0, track->startQuat);
264 }
265
266 void
267 TrackballSetTemporaryRotation(Trackball *track, const double *q)
268 {
269         memmove(track->tempQuat, q, sizeof(double) * 4);
270 }
271
272 void
273 TrackballDrag(Trackball *track, const float *mousePos)
274 {
275     double rot[4];
276     double w1[4], w2[4];
277     double w;
278         NULL_CHECK(track, "TrackballDrag");
279
280     if (track->mode == kTrackballRotateMode) {
281
282         /*  Rotation from the center to the end point  */
283         getRotationToCenter(mousePos[0], mousePos[1], 1.0, w1);
284         w1[1] = -w1[1];
285         w1[2] = -w1[2];
286         w1[3] = -w1[3];
287         
288         //  Accumulate 'start to center' and 'center to end' in this order
289         multiplyQuat(track->startQuat, w1, track->tempQuat);
290     
291     } else if (track->mode == kTrackballTranslateMode) {
292
293         /*  Make an (x, y, 0) vector in viewport coordinate  */
294         TrackballGetPerspective(track, rot);
295         w = (rot[1] * tan(rot[0] * 0.5 * kDeg2Rad) * 2.0);
296         rot[0] = 0.0;
297         rot[1] = (mousePos[0] - track->start[0]) * w;
298         rot[2] = (mousePos[1] - track->start[1]) * w;
299         rot[3] = 0.0;
300         
301         /*  Rotate with the viewport transform to give world coordinate
302          *  y = q* . x . q, where x = original vector, y = transformed vector,
303          *  q = quaternion, q* = conjugate quaternion  */
304         multiplyQuat(rot, track->quat, w1);
305         w2[0] = track->quat[0];
306         w2[1] = -track->quat[1];
307         w2[2] = -track->quat[2];
308         w2[3] = -track->quat[3];
309         multiplyQuat(w2, w1, rot);
310         track->tempTrans[0] = rot[1];
311         track->tempTrans[1] = rot[2];
312         track->tempTrans[2] = rot[3];
313     } else if (track->mode == kTrackballScaleMode) {
314         w = (mousePos[0] - track->start[0]);
315         if (w > 5.0)
316             w = 5.0;
317         else if (w < -5.0)
318             w = -5.0;
319         track->tempScale = w;
320     }
321 }
322
323 void
324 TrackballEndDragging(Trackball *track, const float *mousePos)
325 {
326     double w[4];
327         NULL_CHECK(track, "TrackballEndDragging");
328         if (mousePos != NULL)
329                 TrackballDrag(track, mousePos);
330     multiplyQuat(track->tempQuat, track->quat, w);
331     normalizeQuat(w);
332     track->quat[0] = w[0];
333     track->quat[1] = w[1];
334     track->quat[2] = w[2];
335     track->quat[3] = w[3];
336     track->tempQuat[0] = 1.0;
337     track->tempQuat[1] = track->tempQuat[2] = track->tempQuat[3] = 0.0;
338     track->trans[0] += track->tempTrans[0];
339     track->trans[1] += track->tempTrans[1];
340     track->trans[2] += track->tempTrans[2];
341     track->tempTrans[0] = track->tempTrans[1] = track->tempTrans[2] = 0.0;
342     track->scale += track->tempScale;
343     if (track->scale > 5.0)
344         track->scale = 5.0;
345     else if (track->scale < -5.0)
346         track->scale = -5.0;
347     track->tempScale = 0.0;
348 }
349