4 * Created by Toshi Nagata on 2005/09/17.
5 * Copyright 2005-2008 Toshi Nagata. All rights reserved.
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.
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.
17 #include "Trackball.h"
21 #pragma mark ====== Internal functions ======
24 multiplyQuat(const double *a, const double *b, double *c)
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]);
33 normalizeQuat(double *a)
36 f = 1.0 / sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2] + a[3] * a[3]);
44 getRotationToCenter(double x, double y, double radius, double *quat)
46 const float sqrt2 = 1.41421356 / 2;
51 /* Rotation of 90 degree along (y, -x, 0) */
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) */
65 quat[1] = quat[2] = quat[3] = 0.0;
67 cosAng = sqrt(0.5 * (1.0 + z / radius));
68 sinAng = sqrt(0.5 * (1.0 - z / radius));
71 quat[1] = sinAng * y * r;
72 quat[2] = -sinAng * x * r;
79 rotation2Quat(const double *A, double *q)
81 float ang2; /* The half-angle */
82 float sinAng2; /* sin(half-angle) */
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). */
88 ang2 = A[0] * kDeg2Rad * 0.5; /* Convert from degrees to radians, get the half-angle. */
90 q[0] = A[1] * sinAng2; q[1] = A[2] * sinAng2; q[2] = A[3] * sinAng2;
94 #pragma mark ====== New/Retain/Release ======
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;
106 TrackballRetain(Trackball *track)
113 TrackballRelease(Trackball *track)
115 if (track != NULL && --(track->refCount) == 0) {
121 TrackballGetScale(const Trackball *track)
123 NULL_CHECK(track, "TrackballGetScale");
124 return track->tempScale + track->scale;
127 /* Get the current rotation (in GL format) */
129 TrackballGetRotate(const Trackball *track, double *a)
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);
136 if (fabs(fabs(w[0]) - 1.0) < 1e-7) {
137 /* Identity rotation */
142 /* Quaternion: (cos(theta/2), sin(theta/2)*(x,y,z)) */
143 /* GL rotater: (theta, x, y, z) */
145 a[0] = t * 2.0 * kRad2Deg;
153 /* Get the current translation. The result values should be multiplied with
154 * the dimension of the model. */
156 TrackballGetTranslate(const Trackball *track, double *a)
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];
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. */
168 TrackballGetPerspective(const Trackball *track, double *a)
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;
175 /* Shrink: fovy is fixed at 30 degree, and distance is enlarged */
178 a[1] = kCot15Deg * exp(-kLog10 * f);
181 /* Expand: fovy is reduced while distance fixed at kCot15Deg */
184 a[0] = 30.0 * exp(-kLog10 * f);
190 TrackballGetModifyCount(Trackball *track)
192 return (track != NULL ? track->modifyCount : 0);
196 TrackballReset(Trackball *track)
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;
206 track->modifyCount++;
210 TrackballSetScale(Trackball *track, double scale)
212 NULL_CHECK(track, "TrackballSetScale");
213 track->tempScale = 0;
214 track->scale = scale;
215 track->modifyCount++;
219 TrackballSetRotate(Trackball *track, const double *a)
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++;
234 TrackballSetTranslate(Trackball *track, const double *a)
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++;
244 #pragma mark ====== Mouse operations ======
247 TrackballStartDragging(Trackball *track, const float *mousePos, TrackballMode mode)
249 NULL_CHECK(track, "TrackballStartDragging");
251 /* 1: rotate, 2: translate, 3: scale */
254 /* The start point vector */
255 if (mousePos != NULL) {
256 track->start[0] = mousePos[0];
257 track->start[1] = mousePos[1];
259 track->start[0] = track->start[1] = 0.0;
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);
267 TrackballSetTemporaryRotation(Trackball *track, const double *q)
269 memmove(track->tempQuat, q, sizeof(double) * 4);
273 TrackballDrag(Trackball *track, const float *mousePos)
278 NULL_CHECK(track, "TrackballDrag");
280 if (track->mode == kTrackballRotateMode) {
282 /* Rotation from the center to the end point */
283 getRotationToCenter(mousePos[0], mousePos[1], 1.0, w1);
288 // Accumulate 'start to center' and 'center to end' in this order
289 multiplyQuat(track->startQuat, w1, track->tempQuat);
291 } else if (track->mode == kTrackballTranslateMode) {
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);
297 rot[1] = (mousePos[0] - track->start[0]) * w;
298 rot[2] = (mousePos[1] - track->start[1]) * w;
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]);
319 track->tempScale = w;
324 TrackballEndDragging(Trackball *track, const float *mousePos)
327 NULL_CHECK(track, "TrackballEndDragging");
328 if (mousePos != NULL)
329 TrackballDrag(track, mousePos);
330 multiplyQuat(track->tempQuat, track->quat, 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)
345 else if (track->scale < -5.0)
347 track->tempScale = 0.0;