1/* Copyright (C) 2007-2008 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12#include "android/skin/trackball.h"
13#include "android/skin/image.h"
14#include "android/utils/system.h"
15#include "android/user-events.h"
16#include <math.h>
17
18/***********************************************************************/
19/***********************************************************************/
20/*****                                                             *****/
21/*****       T R A C K   B A L L                                   *****/
22/*****                                                             *****/
23/***********************************************************************/
24/***********************************************************************/
25
26// a 3-d vector
27typedef  double   VectorRec[3];
28typedef  double*  Vector;
29
30/* define FIX16_IS_FLOAT to use floats for computations */
31#define  FIX16_IS_FLOAT
32
33#ifdef FIX16_IS_FLOAT
34typedef float   Fix16;
35#define  FIX16_ONE           1.0
36#define  FIX16_FROM_FLOAT(x)  (x)
37#define  FIX16_TO_FLOAT(x)    (x)
38
39#else
40typedef int     Fix16;
41
42#define  FIX16_SHIFT  16
43#define  FIX16_ONE            (1 << FIX16_SHIFT)
44#define  FIX16_FROM_FLOAT(x)  (Fix16)((x) * FIX16_ONE)
45#define  FIX16_TO_FLOAT(x)    ((x)/(1.0*FIX16_ONE))
46
47#endif
48
49typedef Fix16   Fix16VectorRec[3];
50typedef Fix16*  Fix16Vector;
51
52static Fix16
53fixedvector_len( Fix16Vector  v )
54{
55    double  x = FIX16_TO_FLOAT(v[0]);
56    double  y = FIX16_TO_FLOAT(v[1]);
57    double  z = FIX16_TO_FLOAT(v[2]);
58    double  len = sqrt( x*x + y*y + z*z );
59
60    return FIX16_FROM_FLOAT(len);
61}
62
63static void
64fixedvector_from_vector( Fix16Vector  f, Vector  v )
65{
66    f[0] = FIX16_FROM_FLOAT(v[0]);
67    f[1] = FIX16_FROM_FLOAT(v[1]);
68    f[2] = FIX16_FROM_FLOAT(v[2]);
69}
70
71
72#ifdef FIX16_IS_FLOAT
73static double
74fixedvector_dot( Fix16Vector  u, Fix16Vector  v )
75{
76    return u[0]*v[0] + u[1]*v[1] + u[2]*v[2];
77}
78#else
79static Fix16
80fixedvector_dot( Fix16Vector  u, Fix16Vector  v )
81{
82    long long  t;
83
84    t = (long long)u[0] * v[0] + (long long)u[1] * v[1] + (long long)u[2] * v[2];
85    return (Fix16)(t >> FIX16_SHIFT);
86}
87#endif
88
89static int
90norm( int  dx, int  dy )
91{
92    return (int) sqrt( dx*1.0*dx + dy*1.0*dy );
93}
94
95/*** ROTATOR: used to rotate the reference axis when mouse motion happens
96 ***/
97
98typedef struct
99{
100    VectorRec   d;
101    VectorRec   n;
102    double      angle;
103
104} RotatorRec, *Rotator;
105
106
107#define  ANGLE_FACTOR  (M_PI/200)
108
109static void
110rotator_reset( Rotator  rot, int  dx, int  dy )
111{
112    double  len = sqrt( dx*dx + dy*dy );
113    double  zx, zy;
114
115    if (len < 1e-3 ) {
116        zx = 1.;
117        zy = 0;
118    } else {
119        zx = dx / len;
120        zy = dy / len;
121    }
122    rot->d[0] = zx;
123    rot->d[1] = zy;
124    rot->d[2] = 0.;
125
126    rot->n[0] = -rot->d[1];
127    rot->n[1] =  rot->d[0];
128    rot->n[2] = 0;
129
130    rot->angle = len * ANGLE_FACTOR;
131}
132
133static void
134rotator_apply( Rotator  rot, double*  vec )
135{
136    double   d, n, z, d2, z2, cs, sn;
137
138    /* project on D, N, Z */
139    d = vec[0]*rot->d[0] + vec[1]*rot->d[1];
140    n = vec[0]*rot->n[0] + vec[1]*rot->n[1];
141    z = vec[2];
142
143    /* rotate on D, Z */
144    cs = cos( rot->angle );
145    sn = sin( rot->angle );
146
147    d2 =  cs*d + sn*z;
148    z2 = -sn*d + cs*z;
149
150    /* project on X, Y, Z */
151    vec[0] = d2*rot->d[0] + n*rot->n[0];
152    vec[1] = d2*rot->d[1] + n*rot->n[1];
153    vec[2] = z2;
154}
155
156/*** TRACKBALL OBJECT
157 ***/
158typedef struct { int  x, y, offset, alpha; Fix16VectorRec  f; } SphereCoordRec, *SphereCoord;
159
160typedef struct SkinTrackBall
161{
162    int             diameter;
163    unsigned*       pixels;
164    SDL_Surface*    surface;
165    VectorRec       axes[3];  /* current ball axes */
166
167#define  DOT_GRID        3                        /* number of horizontal and vertical cells per side grid */
168#define  DOT_CELLS       2                        /* number of random dots per cell */
169#define  DOT_MAX         (6*DOT_GRID*DOT_GRID*DOT_CELLS)  /* total number of dots */
170#define  DOT_RANDOM_X    1007                     /* horizontal random range in each cell */
171#define  DOT_RANDOM_Y    1007                     /* vertical random range in each cell */
172
173#define  DOT_THRESHOLD  FIX16_FROM_FLOAT(0.17)
174
175    Fix16VectorRec   dots[ DOT_MAX ];
176
177    SphereCoordRec*  sphere_map;
178    int              sphere_count;
179
180    unsigned         ball_color;
181    unsigned         dot_color;
182    unsigned         ring_color;
183
184    Uint32           ticks_last;  /* ticks since last move */
185    int              acc_x;
186    int              acc_y;
187    int              acc_threshold;
188    double           acc_scale;
189
190    /* rotation applied to events send to the system */
191    SkinRotation     rotation;
192
193} TrackBallRec, *TrackBall;
194
195
196/* The following constants are used to better mimic a real trackball.
197 *
198 * ACC_THRESHOLD is used to filter small ball movements out.
199 * If the length of the relative mouse motion is smaller than this
200 * constant, then no corresponding ball event will be sent to the
201 * system.
202 *
203 * ACC_SCALE is used to scale the relative mouse motion vector into
204 * the corresponding ball motion vector.
205 */
206#define  ACC_THRESHOLD  20
207#define  ACC_SCALE      0.2
208
209static void
210trackball_init( TrackBall  ball, int  diameter, int  ring,
211                unsigned   ball_color, unsigned  dot_color,
212                unsigned   ring_color )
213{
214    int  diameter2 = diameter + ring*2;
215
216    memset( ball, 0, sizeof(*ball) );
217
218    ball->acc_threshold = ACC_THRESHOLD;
219    ball->acc_scale     = ACC_SCALE;
220
221    /* init SDL surface */
222    ball->diameter   = diameter2;
223    ball->ball_color = ball_color;
224    ball->dot_color  = dot_color;
225    ball->ring_color = ring_color;
226
227    ball->rotation   = SKIN_ROTATION_0;
228
229    ball->pixels   = (unsigned*)calloc( diameter2*diameter2, sizeof(unsigned) );
230    ball->surface  = sdl_surface_from_argb32( ball->pixels, diameter2, diameter2 );
231
232    /* init axes */
233    ball->axes[0][0] = 1.; ball->axes[0][1] = 0.; ball->axes[0][2] = 0.;
234    ball->axes[1][0] = 0.; ball->axes[1][1] = 1.; ball->axes[1][2] = 0.;
235    ball->axes[2][0] = 0.; ball->axes[2][1] = 0.; ball->axes[2][2] = 1.;
236
237    /* init dots */
238    {
239        int  side, nn = 0;
240
241        for (side = 0; side < 6; side++) {
242            VectorRec  origin, axis1, axis2;
243            int        xx, yy;
244
245            switch (side) {
246            case 0:
247                origin[0] = -1; origin[1] = -1; origin[2] = +1;
248                axis1 [0] =  1; axis1 [1] =  0; axis1 [2] =  0;
249                axis2 [0] =  0; axis2 [1] =  1; axis2 [2] =  0;
250                break;
251            case 1:
252                origin[0] = -1; origin[1] = -1; origin[2] = -1;
253                axis1 [0] =  1; axis1 [1] =  0; axis1 [2] =  0;
254                axis2 [0] =  0; axis2 [1] =  1; axis2 [2] =  0;
255                break;
256            case 2:
257                origin[0] = +1; origin[1] = -1; origin[2] = -1;
258                axis1 [0] =  0; axis1 [1] =  0; axis1 [2] =  1;
259                axis2 [0] =  0; axis2 [1] =  1; axis2 [2] =  0;
260                break;
261            case 3:
262                origin[0] = -1; origin[1] = -1; origin[2] = -1;
263                axis1 [0] =  0; axis1 [1] =  0; axis1 [2] =  1;
264                axis2 [0] =  0; axis2 [1] =  1; axis2 [2] =  0;
265                break;
266            case 4:
267                origin[0] = -1; origin[1] = -1; origin[2] = -1;
268                axis1 [0] =  1; axis1 [1] =  0; axis1 [2] =  0;
269                axis2 [0] =  0; axis2 [1] =  0; axis2 [2] =  1;
270                break;
271            default:
272                origin[0] = -1; origin[1] = +1; origin[2] = -1;
273                axis1 [0] =  1; axis1 [1] =  0; axis1 [2] =  0;
274                axis2 [0] =  0; axis2 [1] =  0; axis2 [2] =  1;
275            }
276
277            for (xx = 0; xx < DOT_GRID; xx++) {
278                double  tx = xx*(2./DOT_GRID);
279                for (yy = 0; yy < DOT_GRID; yy++) {
280                    double  ty = yy*(2./DOT_GRID);
281                    double  x0  = origin[0] + axis1[0]*tx + axis2[0]*ty;
282                    double  y0  = origin[1] + axis1[1]*tx + axis2[1]*ty;
283                    double  z0  = origin[2] + axis1[2]*tx + axis2[2]*ty;
284                    int     cc;
285                    for (cc = 0; cc < DOT_CELLS; cc++) {
286                        double  h = (rand() % DOT_RANDOM_X)/((double)DOT_RANDOM_X*DOT_GRID/2);
287                        double  v = (rand() % DOT_RANDOM_Y)/((double)DOT_RANDOM_Y*DOT_GRID/2);
288                        double  x = x0 + axis1[0]*h + axis2[0]*v;
289                        double  y = y0 + axis1[1]*h + axis2[1]*v;
290                        double  z = z0 + axis1[2]*h + axis2[2]*v;
291                        double  invlen = 1/sqrt( x*x + y*y + z*z );
292
293                        ball->dots[nn][0] = FIX16_FROM_FLOAT(x*invlen);
294                        ball->dots[nn][1] = FIX16_FROM_FLOAT(y*invlen);
295                        ball->dots[nn][2] = FIX16_FROM_FLOAT(z*invlen);
296                        nn++;
297                    }
298                }
299            }
300        }
301    }
302
303    /* init sphere */
304    {
305        int     diameter2 = diameter + 2*ring;
306        double  radius    = diameter*0.5;
307        double  radius2   = diameter2*0.5;
308        int     xx, yy;
309        int     empty = 0, total = 0;
310
311        ball->sphere_map = calloc( diameter2*diameter2, sizeof(SphereCoordRec) );
312
313        for (yy = 0; yy < diameter2; yy++) {
314            for (xx = 0; xx < diameter2; xx++) {
315                double       x0    = xx - radius2;
316                double       y0    = yy - radius2;
317                double       r0    = sqrt( x0*x0 + y0*y0 );
318                SphereCoord  coord = &ball->sphere_map[total];
319
320                if (r0 <= radius) {  /* ball pixel */
321                    double  rx = x0/radius;
322                    double  ry = y0/radius;
323                    double  rz = sqrt( 1.0 - rx*rx - ry*ry );
324
325                    coord->x      = xx;
326                    coord->y      = yy;
327                    coord->offset = xx + yy*diameter2;
328                    coord->alpha  = 256;
329                    coord->f[0]   = FIX16_FROM_FLOAT(rx);
330                    coord->f[1]   = FIX16_FROM_FLOAT(ry);
331                    coord->f[2]   = FIX16_FROM_FLOAT(rz);
332                    if (r0 >= radius-1.) {
333                        coord->alpha = 256*(radius - r0);
334                    }
335                    /* illumination model */
336                    {
337#define  LIGHT_X         -2.0
338#define  LIGHT_Y         -2.5
339#define  LIGHT_Z          5.0
340
341                        double  lx = LIGHT_X - rx;
342                        double  ly = LIGHT_Y - ry;
343                        double  lz = LIGHT_Z - rz;
344                        double  lir = 1/sqrt(lx*lx + ly*ly + lz*lz);
345                        double  cosphi = lir*(lx*rx + ly*ry + lz*rz);
346                        double  scale  = 1.1*cosphi + 0.3;
347
348                        if (scale < 0)
349                            scale = 0;
350
351                        coord->alpha = coord->alpha * scale;
352                    }
353                    total++;
354                } else if (r0 <= radius2) { /* ring pixel */
355                    coord->x      = xx;
356                    coord->y      = yy;
357                    coord->offset = xx + yy*diameter2;
358                    coord->alpha  = 0;
359                    if (r0 >= radius2-1.) {
360                        coord->alpha = -256*(r0 - (radius2-1.));
361                    }
362                    total++;
363
364                } else   /* outside pixel */
365                    empty++;
366            }
367        }
368        ball->sphere_count = total;
369    }
370}
371
372static int
373trackball_contains( TrackBall  ball, int  x, int  y )
374{
375    return ( (unsigned)(x) < (unsigned)ball->diameter &&
376             (unsigned)(y) < (unsigned)ball->diameter );
377}
378
379static void
380trackball_done( TrackBall  ball )
381{
382    free( ball->sphere_map );
383    ball->sphere_map   = NULL;
384    ball->sphere_count = 0;
385
386    if (ball->surface) {
387        SDL_FreeSurface( ball->surface );
388        ball->surface = NULL;
389    }
390
391    if (ball->pixels) {
392        free( ball->pixels );
393        ball->pixels = NULL;
394    }
395}
396
397/*** TRACKBALL SPHERE PIXELS
398 ***/
399static unsigned
400color_blend( unsigned  from, unsigned  to,  int  alpha )
401{
402    unsigned  from_ag    = (from >> 8) & 0x00ff00ff;
403    unsigned  to_ag      = (to >> 8) & 0x00ff00ff;
404    unsigned  from_rb    = from & 0x00ff00ff;
405    unsigned  to_rb      = to & 0x00ff00ff;
406    unsigned  result_ag = (from_ag + (alpha*(to_ag - from_ag) >> 8)) & 0xff00ff;
407    unsigned  result_rb = (from_rb + (alpha*(to_rb - from_rb) >> 8)) & 0xff00ff;
408
409    return (result_ag << 8) | result_rb;
410}
411
412static int
413trackball_move( TrackBall  ball,  int  dx, int  dy )
414{
415    RotatorRec  rot[1];
416    Uint32      now = SDL_GetTicks();
417
418    ball->acc_x += dx;
419    ball->acc_y += dy;
420
421    if ( norm( ball->acc_x, ball->acc_y ) > ball->acc_threshold )
422    {
423        int  ddx = ball->acc_x * ball->acc_scale;
424        int  ddy = ball->acc_y * ball->acc_scale;
425        int  ddt;
426
427        ball->acc_x = 0;
428        ball->acc_y = 0;
429
430        switch (ball->rotation) {
431        case SKIN_ROTATION_0:
432            break;
433
434        case SKIN_ROTATION_90:
435            ddt = ddx;
436            ddx = ddy;
437            ddy = -ddt;
438            break;
439
440        case SKIN_ROTATION_180:
441            ddx = -ddx;
442            ddy = -ddy;
443            break;
444
445        case SKIN_ROTATION_270:
446            ddt = ddx;
447            ddx = -ddy;
448            ddy = ddt;
449            break;
450        }
451
452        user_event_mouse(ddx, ddy, 1, 0);
453    }
454
455    rotator_reset( rot, dx, dy );
456    rotator_apply( rot, ball->axes[0] );
457    rotator_apply( rot, ball->axes[1] );
458    rotator_apply( rot, ball->axes[2] );
459
460    if ( ball->ticks_last == 0 )
461        ball->ticks_last = now;
462    else if ( now > ball->ticks_last + (1000/60) ) {
463        ball->ticks_last = now;
464        return 1;
465    }
466    return 0;
467}
468
469#define  BACK_COLOR   0x00000000
470#define  LIGHT_COLOR  0xffffffff
471
472static void
473trackball_refresh( TrackBall  ball )
474{
475    int             diameter = ball->diameter;
476    unsigned*       pixels   = ball->pixels;
477    Fix16VectorRec  faxes[3];
478    Fix16           dot_threshold = DOT_THRESHOLD * diameter;
479    int             nn;
480
481    SDL_LockSurface( ball->surface );
482
483    fixedvector_from_vector( (Fix16Vector)&faxes[0], (Vector)&ball->axes[0] );
484    fixedvector_from_vector( (Fix16Vector)&faxes[1], (Vector)&ball->axes[1] );
485    fixedvector_from_vector( (Fix16Vector)&faxes[2], (Vector)&ball->axes[2] );
486
487    for (nn = 0; nn < ball->sphere_count; nn++) {
488        SphereCoord  coord = &ball->sphere_map[nn];
489        unsigned     color = BACK_COLOR;
490
491        if (coord->alpha > 0) {
492            /* are we near one of the points ? */
493            Fix16  ax = fixedvector_dot( (Fix16Vector)&coord->f, (Fix16Vector)&faxes[0] );
494            Fix16  ay = fixedvector_dot( (Fix16Vector)&coord->f, (Fix16Vector)&faxes[1] );
495            Fix16  az = fixedvector_dot( (Fix16Vector)&coord->f, (Fix16Vector)&faxes[2] );
496
497            Fix16  best_dist = FIX16_ONE;
498            int    pp;
499
500            color = ball->ball_color;
501
502            for (pp = 0; pp < DOT_MAX; pp++) {
503                Fix16VectorRec  d;
504                Fix16           dist;
505
506                d[0] = ball->dots[pp][0] - ax;
507                d[1] = ball->dots[pp][1] - ay;
508                d[2] = ball->dots[pp][2] - az;
509
510                if (d[0] > dot_threshold || d[0] < -dot_threshold ||
511                    d[1] > dot_threshold || d[1] < -dot_threshold ||
512                    d[2] > dot_threshold || d[2] < -dot_threshold )
513                    continue;
514
515                dist = fixedvector_len( (Fix16Vector)&d );
516
517                if (dist < best_dist)
518                    best_dist = dist;
519            }
520            if (best_dist < DOT_THRESHOLD) {
521                int  a = 256*(DOT_THRESHOLD - best_dist) / DOT_THRESHOLD;
522                color = color_blend( color, ball->dot_color, a );
523            }
524
525            if (coord->alpha < 256) {
526                int  a = coord->alpha;
527                color = color_blend( ball->ring_color, color, a );
528            }
529            else if (coord->alpha > 256) {
530                int  a = (coord->alpha - 256);
531                color = color_blend( color, LIGHT_COLOR, a );
532            }
533        }
534        else /* coord->alpha <= 0 */
535        {
536            color = ball->ring_color;
537
538            if (coord->alpha < 0) {
539                int  a = -coord->alpha;
540                color = color_blend( color, BACK_COLOR, a );
541            }
542        }
543
544        pixels[coord->x + diameter*coord->y] = color;
545    }
546    SDL_UnlockSurface( ball->surface );
547}
548
549void
550trackball_draw( TrackBall  ball, int  x, int  y, SDL_Surface*  dst )
551{
552    SDL_Rect  d;
553
554    d.x = x;
555    d.y = y;
556    d.w = ball->diameter;
557    d.h = ball->diameter;
558
559    SDL_BlitSurface( ball->surface, NULL, dst, &d );
560    SDL_UpdateRects( dst, 1, &d );
561}
562
563
564SkinTrackBall*
565skin_trackball_create  ( SkinTrackBallParameters*  params )
566{
567    TrackBall  ball;
568
569    ANEW0(ball);
570    trackball_init( ball,
571                    params->diameter,
572                    params->ring,
573                    params->ball_color,
574                    params->dot_color,
575                    params->ring_color );
576    return  ball;
577}
578
579int
580skin_trackball_contains( SkinTrackBall*  ball, int  x, int  y )
581{
582    return  trackball_contains(ball, x, y);
583}
584
585int
586skin_trackball_move( SkinTrackBall*  ball, int  dx, int  dy )
587{
588    return  trackball_move(ball, dx, dy);
589}
590
591void
592skin_trackball_refresh ( SkinTrackBall*  ball )
593{
594    trackball_refresh(ball);
595}
596
597void
598skin_trackball_draw( SkinTrackBall*  ball, int  x, int  y, SDL_Surface*  dst )
599{
600    trackball_draw(ball, x, y, dst);
601}
602
603void
604skin_trackball_destroy ( SkinTrackBall*  ball )
605{
606    if (ball) {
607        trackball_done(ball);
608        AFREE(ball);
609    }
610}
611
612void
613skin_trackball_rect( SkinTrackBall*  ball, SDL_Rect*  rect )
614{
615    rect->x = 0;
616    rect->y = 0;
617    rect->w = ball->diameter;
618    rect->h = ball->diameter;
619}
620
621
622void
623skin_trackball_set_rotation( SkinTrackBall*  ball, SkinRotation  rotation )
624{
625    ball->rotation = rotation & 3;
626}
627