/* Copyright (C) 2007-2008 The Android Open Source Project ** ** This software is licensed under the terms of the GNU General Public ** License version 2, as published by the Free Software Foundation, and ** may be copied, distributed, and modified under those terms. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. */ #include "android/skin/trackball.h" #include "android/skin/image.h" #include "android/utils/system.h" #include "user-events.h" #include /***********************************************************************/ /***********************************************************************/ /***** *****/ /***** T R A C K B A L L *****/ /***** *****/ /***********************************************************************/ /***********************************************************************/ // a 3-d vector typedef double VectorRec[3]; typedef double* Vector; /* define FIX16_IS_FLOAT to use floats for computations */ #define FIX16_IS_FLOAT #ifdef FIX16_IS_FLOAT typedef float Fix16; #define FIX16_ONE 1.0 #define FIX16_FROM_FLOAT(x) (x) #define FIX16_TO_FLOAT(x) (x) #else typedef int Fix16; #define FIX16_SHIFT 16 #define FIX16_ONE (1 << FIX16_SHIFT) #define FIX16_FROM_FLOAT(x) (Fix16)((x) * FIX16_ONE) #define FIX16_TO_FLOAT(x) ((x)/(1.0*FIX16_ONE)) #endif typedef Fix16 Fix16VectorRec[3]; typedef Fix16* Fix16Vector; static Fix16 fixedvector_len( Fix16Vector v ) { double x = FIX16_TO_FLOAT(v[0]); double y = FIX16_TO_FLOAT(v[1]); double z = FIX16_TO_FLOAT(v[2]); double len = sqrt( x*x + y*y + z*z ); return FIX16_FROM_FLOAT(len); } static void fixedvector_from_vector( Fix16Vector f, Vector v ) { f[0] = FIX16_FROM_FLOAT(v[0]); f[1] = FIX16_FROM_FLOAT(v[1]); f[2] = FIX16_FROM_FLOAT(v[2]); } #ifdef FIX16_IS_FLOAT static double fixedvector_dot( Fix16Vector u, Fix16Vector v ) { return u[0]*v[0] + u[1]*v[1] + u[2]*v[2]; } #else static Fix16 fixedvector_dot( Fix16Vector u, Fix16Vector v ) { long long t; t = (long long)u[0] * v[0] + (long long)u[1] * v[1] + (long long)u[2] * v[2]; return (Fix16)(t >> FIX16_SHIFT); } #endif static int norm( int dx, int dy ) { return (int) sqrt( dx*1.0*dx + dy*1.0*dy ); } /*** ROTATOR: used to rotate the reference axis when mouse motion happens ***/ typedef struct { VectorRec d; VectorRec n; double angle; } RotatorRec, *Rotator; #define ANGLE_FACTOR (M_PI/200) static void rotator_reset( Rotator rot, int dx, int dy ) { double len = sqrt( dx*dx + dy*dy ); double zx, zy; if (len < 1e-3 ) { zx = 1.; zy = 0; } else { zx = dx / len; zy = dy / len; } rot->d[0] = zx; rot->d[1] = zy; rot->d[2] = 0.; rot->n[0] = -rot->d[1]; rot->n[1] = rot->d[0]; rot->n[2] = 0; rot->angle = len * ANGLE_FACTOR; } static void rotator_apply( Rotator rot, double* vec ) { double d, n, z, d2, z2, cs, sn; /* project on D, N, Z */ d = vec[0]*rot->d[0] + vec[1]*rot->d[1]; n = vec[0]*rot->n[0] + vec[1]*rot->n[1]; z = vec[2]; /* rotate on D, Z */ cs = cos( rot->angle ); sn = sin( rot->angle ); d2 = cs*d + sn*z; z2 = -sn*d + cs*z; /* project on X, Y, Z */ vec[0] = d2*rot->d[0] + n*rot->n[0]; vec[1] = d2*rot->d[1] + n*rot->n[1]; vec[2] = z2; } /*** TRACKBALL OBJECT ***/ typedef struct { int x, y, offset, alpha; Fix16VectorRec f; } SphereCoordRec, *SphereCoord; typedef struct SkinTrackBall { int diameter; unsigned* pixels; SDL_Surface* surface; VectorRec axes[3]; /* current ball axes */ #define DOT_GRID 3 /* number of horizontal and vertical cells per side grid */ #define DOT_CELLS 2 /* number of random dots per cell */ #define DOT_MAX (6*DOT_GRID*DOT_GRID*DOT_CELLS) /* total number of dots */ #define DOT_RANDOM_X 1007 /* horizontal random range in each cell */ #define DOT_RANDOM_Y 1007 /* vertical random range in each cell */ #define DOT_THRESHOLD FIX16_FROM_FLOAT(0.17) Fix16VectorRec dots[ DOT_MAX ]; SphereCoordRec* sphere_map; int sphere_count; unsigned ball_color; unsigned dot_color; unsigned ring_color; Uint32 ticks_last; /* ticks since last move */ int acc_x; int acc_y; int acc_threshold; double acc_scale; /* rotation applied to events send to the system */ SkinRotation rotation; } TrackBallRec, *TrackBall; /* The following constants are used to better mimic a real trackball. * * ACC_THRESHOLD is used to filter small ball movements out. * If the length of the relative mouse motion is smaller than this * constant, then no corresponding ball event will be sent to the * system. * * ACC_SCALE is used to scale the relative mouse motion vector into * the corresponding ball motion vector. */ #define ACC_THRESHOLD 20 #define ACC_SCALE 0.2 static void trackball_init( TrackBall ball, int diameter, int ring, unsigned ball_color, unsigned dot_color, unsigned ring_color ) { int diameter2 = diameter + ring*2; memset( ball, 0, sizeof(*ball) ); ball->acc_threshold = ACC_THRESHOLD; ball->acc_scale = ACC_SCALE; /* init SDL surface */ ball->diameter = diameter2; ball->ball_color = ball_color; ball->dot_color = dot_color; ball->ring_color = ring_color; ball->rotation = SKIN_ROTATION_0; ball->pixels = (unsigned*)calloc( diameter2*diameter2, sizeof(unsigned) ); ball->surface = sdl_surface_from_argb32( ball->pixels, diameter2, diameter2 ); /* init axes */ ball->axes[0][0] = 1.; ball->axes[0][1] = 0.; ball->axes[0][2] = 0.; ball->axes[1][0] = 0.; ball->axes[1][1] = 1.; ball->axes[1][2] = 0.; ball->axes[2][0] = 0.; ball->axes[2][1] = 0.; ball->axes[2][2] = 1.; /* init dots */ { int side, nn = 0; for (side = 0; side < 6; side++) { VectorRec origin, axis1, axis2; int xx, yy; switch (side) { case 0: origin[0] = -1; origin[1] = -1; origin[2] = +1; axis1 [0] = 1; axis1 [1] = 0; axis1 [2] = 0; axis2 [0] = 0; axis2 [1] = 1; axis2 [2] = 0; break; case 1: origin[0] = -1; origin[1] = -1; origin[2] = -1; axis1 [0] = 1; axis1 [1] = 0; axis1 [2] = 0; axis2 [0] = 0; axis2 [1] = 1; axis2 [2] = 0; break; case 2: origin[0] = +1; origin[1] = -1; origin[2] = -1; axis1 [0] = 0; axis1 [1] = 0; axis1 [2] = 1; axis2 [0] = 0; axis2 [1] = 1; axis2 [2] = 0; break; case 3: origin[0] = -1; origin[1] = -1; origin[2] = -1; axis1 [0] = 0; axis1 [1] = 0; axis1 [2] = 1; axis2 [0] = 0; axis2 [1] = 1; axis2 [2] = 0; break; case 4: origin[0] = -1; origin[1] = -1; origin[2] = -1; axis1 [0] = 1; axis1 [1] = 0; axis1 [2] = 0; axis2 [0] = 0; axis2 [1] = 0; axis2 [2] = 1; break; default: origin[0] = -1; origin[1] = +1; origin[2] = -1; axis1 [0] = 1; axis1 [1] = 0; axis1 [2] = 0; axis2 [0] = 0; axis2 [1] = 0; axis2 [2] = 1; } for (xx = 0; xx < DOT_GRID; xx++) { double tx = xx*(2./DOT_GRID); for (yy = 0; yy < DOT_GRID; yy++) { double ty = yy*(2./DOT_GRID); double x0 = origin[0] + axis1[0]*tx + axis2[0]*ty; double y0 = origin[1] + axis1[1]*tx + axis2[1]*ty; double z0 = origin[2] + axis1[2]*tx + axis2[2]*ty; int cc; for (cc = 0; cc < DOT_CELLS; cc++) { double h = (rand() % DOT_RANDOM_X)/((double)DOT_RANDOM_X*DOT_GRID/2); double v = (rand() % DOT_RANDOM_Y)/((double)DOT_RANDOM_Y*DOT_GRID/2); double x = x0 + axis1[0]*h + axis2[0]*v; double y = y0 + axis1[1]*h + axis2[1]*v; double z = z0 + axis1[2]*h + axis2[2]*v; double invlen = 1/sqrt( x*x + y*y + z*z ); ball->dots[nn][0] = FIX16_FROM_FLOAT(x*invlen); ball->dots[nn][1] = FIX16_FROM_FLOAT(y*invlen); ball->dots[nn][2] = FIX16_FROM_FLOAT(z*invlen); nn++; } } } } } /* init sphere */ { int diameter2 = diameter + 2*ring; double radius = diameter*0.5; double radius2 = diameter2*0.5; int xx, yy; int empty = 0, total = 0; ball->sphere_map = calloc( diameter2*diameter2, sizeof(SphereCoordRec) ); for (yy = 0; yy < diameter2; yy++) { for (xx = 0; xx < diameter2; xx++) { double x0 = xx - radius2; double y0 = yy - radius2; double r0 = sqrt( x0*x0 + y0*y0 ); SphereCoord coord = &ball->sphere_map[total]; if (r0 <= radius) { /* ball pixel */ double rx = x0/radius; double ry = y0/radius; double rz = sqrt( 1.0 - rx*rx - ry*ry ); coord->x = xx; coord->y = yy; coord->offset = xx + yy*diameter2; coord->alpha = 256; coord->f[0] = FIX16_FROM_FLOAT(rx); coord->f[1] = FIX16_FROM_FLOAT(ry); coord->f[2] = FIX16_FROM_FLOAT(rz); if (r0 >= radius-1.) { coord->alpha = 256*(radius - r0); } /* illumination model */ { #define LIGHT_X -2.0 #define LIGHT_Y -2.5 #define LIGHT_Z 5.0 double lx = LIGHT_X - rx; double ly = LIGHT_Y - ry; double lz = LIGHT_Z - rz; double lir = 1/sqrt(lx*lx + ly*ly + lz*lz); double cosphi = lir*(lx*rx + ly*ry + lz*rz); double scale = 1.1*cosphi + 0.3; if (scale < 0) scale = 0; coord->alpha = coord->alpha * scale; } total++; } else if (r0 <= radius2) { /* ring pixel */ coord->x = xx; coord->y = yy; coord->offset = xx + yy*diameter2; coord->alpha = 0; if (r0 >= radius2-1.) { coord->alpha = -256*(r0 - (radius2-1.)); } total++; } else /* outside pixel */ empty++; } } ball->sphere_count = total; } } static int trackball_contains( TrackBall ball, int x, int y ) { return ( (unsigned)(x) < (unsigned)ball->diameter && (unsigned)(y) < (unsigned)ball->diameter ); } static void trackball_done( TrackBall ball ) { free( ball->sphere_map ); ball->sphere_map = NULL; ball->sphere_count = 0; if (ball->surface) { SDL_FreeSurface( ball->surface ); ball->surface = NULL; } if (ball->pixels) { free( ball->pixels ); ball->pixels = NULL; } } /*** TRACKBALL SPHERE PIXELS ***/ static unsigned color_blend( unsigned from, unsigned to, int alpha ) { unsigned from_ag = (from >> 8) & 0x00ff00ff; unsigned to_ag = (to >> 8) & 0x00ff00ff; unsigned from_rb = from & 0x00ff00ff; unsigned to_rb = to & 0x00ff00ff; unsigned result_ag = (from_ag + (alpha*(to_ag - from_ag) >> 8)) & 0xff00ff; unsigned result_rb = (from_rb + (alpha*(to_rb - from_rb) >> 8)) & 0xff00ff; return (result_ag << 8) | result_rb; } static int trackball_move( TrackBall ball, int dx, int dy ) { RotatorRec rot[1]; Uint32 now = SDL_GetTicks(); ball->acc_x += dx; ball->acc_y += dy; if ( norm( ball->acc_x, ball->acc_y ) > ball->acc_threshold ) { int ddx = ball->acc_x * ball->acc_scale; int ddy = ball->acc_y * ball->acc_scale; int ddt; ball->acc_x = 0; ball->acc_y = 0; switch (ball->rotation) { case SKIN_ROTATION_0: break; case SKIN_ROTATION_90: ddt = ddx; ddx = ddy; ddy = -ddt; break; case SKIN_ROTATION_180: ddx = -ddx; ddy = -ddy; break; case SKIN_ROTATION_270: ddt = ddx; ddx = -ddy; ddy = ddt; break; } user_event_mouse(ddx, ddy, 1, 0); } rotator_reset( rot, dx, dy ); rotator_apply( rot, ball->axes[0] ); rotator_apply( rot, ball->axes[1] ); rotator_apply( rot, ball->axes[2] ); if ( ball->ticks_last == 0 ) ball->ticks_last = now; else if ( now > ball->ticks_last + (1000/60) ) { ball->ticks_last = now; return 1; } return 0; } #define BACK_COLOR 0x00000000 #define LIGHT_COLOR 0xffffffff static void trackball_refresh( TrackBall ball ) { int diameter = ball->diameter; unsigned* pixels = ball->pixels; Fix16VectorRec faxes[3]; Fix16 dot_threshold = DOT_THRESHOLD * diameter; int nn; SDL_LockSurface( ball->surface ); fixedvector_from_vector( (Fix16Vector)&faxes[0], (Vector)&ball->axes[0] ); fixedvector_from_vector( (Fix16Vector)&faxes[1], (Vector)&ball->axes[1] ); fixedvector_from_vector( (Fix16Vector)&faxes[2], (Vector)&ball->axes[2] ); for (nn = 0; nn < ball->sphere_count; nn++) { SphereCoord coord = &ball->sphere_map[nn]; unsigned color = BACK_COLOR; if (coord->alpha > 0) { /* are we near one of the points ? */ Fix16 ax = fixedvector_dot( (Fix16Vector)&coord->f, (Fix16Vector)&faxes[0] ); Fix16 ay = fixedvector_dot( (Fix16Vector)&coord->f, (Fix16Vector)&faxes[1] ); Fix16 az = fixedvector_dot( (Fix16Vector)&coord->f, (Fix16Vector)&faxes[2] ); Fix16 best_dist = FIX16_ONE; int pp; color = ball->ball_color; for (pp = 0; pp < DOT_MAX; pp++) { Fix16VectorRec d; Fix16 dist; d[0] = ball->dots[pp][0] - ax; d[1] = ball->dots[pp][1] - ay; d[2] = ball->dots[pp][2] - az; if (d[0] > dot_threshold || d[0] < -dot_threshold || d[1] > dot_threshold || d[1] < -dot_threshold || d[2] > dot_threshold || d[2] < -dot_threshold ) continue; dist = fixedvector_len( (Fix16Vector)&d ); if (dist < best_dist) best_dist = dist; } if (best_dist < DOT_THRESHOLD) { int a = 256*(DOT_THRESHOLD - best_dist) / DOT_THRESHOLD; color = color_blend( color, ball->dot_color, a ); } if (coord->alpha < 256) { int a = coord->alpha; color = color_blend( ball->ring_color, color, a ); } else if (coord->alpha > 256) { int a = (coord->alpha - 256); color = color_blend( color, LIGHT_COLOR, a ); } } else /* coord->alpha <= 0 */ { color = ball->ring_color; if (coord->alpha < 0) { int a = -coord->alpha; color = color_blend( color, BACK_COLOR, a ); } } pixels[coord->x + diameter*coord->y] = color; } SDL_UnlockSurface( ball->surface ); } void trackball_draw( TrackBall ball, int x, int y, SDL_Surface* dst ) { SDL_Rect d; d.x = x; d.y = y; d.w = ball->diameter; d.h = ball->diameter; SDL_BlitSurface( ball->surface, NULL, dst, &d ); SDL_UpdateRects( dst, 1, &d ); } SkinTrackBall* skin_trackball_create ( SkinTrackBallParameters* params ) { TrackBall ball; ANEW0(ball); trackball_init( ball, params->diameter, params->ring, params->ball_color, params->dot_color, params->ring_color ); return ball; } int skin_trackball_contains( SkinTrackBall* ball, int x, int y ) { return trackball_contains(ball, x, y); } int skin_trackball_move( SkinTrackBall* ball, int dx, int dy ) { return trackball_move(ball, dx, dy); } void skin_trackball_refresh ( SkinTrackBall* ball ) { trackball_refresh(ball); } void skin_trackball_draw( SkinTrackBall* ball, int x, int y, SDL_Surface* dst ) { trackball_draw(ball, x, y, dst); } void skin_trackball_destroy ( SkinTrackBall* ball ) { if (ball) { trackball_done(ball); AFREE(ball); } } void skin_trackball_rect( SkinTrackBall* ball, SDL_Rect* rect ) { rect->x = 0; rect->y = 0; rect->w = ball->diameter; rect->h = ball->diameter; } void skin_trackball_set_rotation( SkinTrackBall* ball, SkinRotation rotation ) { ball->rotation = rotation & 3; }