public class Sphere 
implements   Drawable 
{
    protected static float lx, ly, lz;

    public float x;
    public float y;
    public float r;
    public int   color;
    public static int alpha = 255;

    private static float MAX_FLOAT_R = 30.0f;
    private static int   MAX_R       = (int)(MAX_FLOAT_R+.5);
    private static int   cache[][];

    public 
    Sphere()
    {
        x     = 0;
        y     = 0;
        r     = 1;
        color = 0xffeeccaa;
        lx    = Float.MAX_VALUE;
        ly    = Float.MAX_VALUE;
        lz    = Float.MAX_VALUE;
        cache = new int[2+MAX_R*2][MAX_R*2+2];
    }

    public 
    Sphere( float x, float y, float radius, int fillColor )
    {
        this.x     = x;
        this.y     = y;
        this.r     = radius;
        this.color = fillColor;
        lx    = Float.MAX_VALUE;
        ly    = Float.MAX_VALUE;
        lz    = Float.MAX_VALUE;
        cache = new int[MAX_R*2+2][MAX_R*2+2];
    }

    public static void 
    setLightDir( float x, float y, float z ) {
        float t = 1.0f/(float) Math.sqrt(x*x + y*y + z*z);
        if ( ( lx == x*t ) && 
             ( ly == y*t ) && 
             ( lz == z*t ) ) {
            return;
        }
        // System.out.println( "Setting light direction" );
        
        lx = x*t;
        ly = y*t;
        lz = z*t;

        float height;
        float r_sq     = MAX_R*MAX_R;

        float normalized_x, normalized_y, normalized_z; 
        
        int   i_index;
        int   j_index;

        for ( int i=-MAX_R-1; i<MAX_R+1; i++ ) {
            int i_sq = i*i;
            i_index = i+MAX_R;
            for ( int j=-MAX_R; j<MAX_R; j++ ) {
                height = r_sq - i_sq -j*j;
                if ( height >= 0 ) {
                    j_index = j+MAX_R;

                    height       = (float)Math.sqrt( height );

                    normalized_x = i      / MAX_FLOAT_R;
                    normalized_y = j      / MAX_FLOAT_R;
                    normalized_z = height / MAX_FLOAT_R;
                    // Doing dot product
                    cache[i_index][j_index] = (int)( ( normalized_x*lx +
                                                       normalized_y*ly +
                                                       normalized_z*lz )*255 );
                    // Make sure the dimmest point is half the argb
                    if ( cache[i_index][j_index] < 0 )
                        cache[i_index][j_index] = 0;                                   
                }
            }
        }
    }

    public void 
    Draw( Raster raster )
    {
        int new_color;
        int scale;
        int red        = ( color & 0x00ff0000 ) >> 16;
        int green      = ( color & 0x0000ff00 ) >> 8;
        int blue       = ( color & 0x000000ff );
        int r2         = (int) r;
        int half_red   = red   / 2;
        int half_green = green / 2;
        int half_blue  = blue  / 2;
        int new_red;
        int new_green;
        int new_blue;
        
        // OK to use floor, since I assume x0, y0 will be positive.
        // Horizontal interpolation scale 1 for float X0, Y0
        int his1 = (int)( 256 * ( 1.0 - ( x - Math.floor( x ) ) ) );
        // Horizontal interpolation scale 2 for float X0, Y0
        int his2 = 256 - his1;
        // Vertical interpolation scale 1 for float X0, Y0
        int vis1 = (int)( 256 * ( 1.0 - ( y - Math.floor( y ) ) ) );
        // Vertical interpolation scale 2 for float X0, Y0
        int vis2 = 256 - vis1;
        
        for ( int i=-r2; i<r2; i++ ) {
            for ( int j=-r2; j<r2; j++ ) {
                if ( i*i + j*j < r*r ) {
                    // Get the scale
                    scale = (
                        cache[MAX_R+i    *MAX_R/(int)r][MAX_R+j    *MAX_R/(int)r] * (his1 + vis1) +
                        cache[MAX_R+(i+1)*MAX_R/(int)r][MAX_R+j    *MAX_R/(int)r] * his2  +
                        cache[MAX_R+i    *MAX_R/(int)r][MAX_R+(j+1)*MAX_R/(int)r] * vis2  ) / 512;
                        
                    // Scale the rgb values
                    new_red   = scale * half_red   / 255 + half_red;
                    new_green = scale * half_green / 255 + half_green;
                    new_blue  = scale * half_blue  / 255 + half_blue;
                    
                    // Construct the new color
                    new_color = ( alpha     << 24 ) | 
                                ( new_red    << 16 ) |
                                ( new_green << 8  ) |
                                ( new_blue        );
                    
                    // Set the pixel            
                    raster.setPixel( new_color, (int)x+i, (int)y+j );
                }
            }
        }
    }
	
	// This is a faster and more efficient version.
    public void 
    Draw2( Raster raster )
    {
        int r2 = (int) this.r;
        int new_color;
        int scale;
        int red        = ( color & 0x00ff0000 ) >> 16;
        int green      = ( color & 0x0000ff00 ) >> 8;
        int blue       = ( color & 0x000000ff );
        int half_red   = red   / 2;
        int half_green = green / 2;
        int half_blue  = blue  / 2;
        int new_red;
        int new_green;
        int new_blue;
        
        for ( int i=-r2; i<r2; i++ ) {
            for ( int j=-r2; j<r2; j++ ) {
                if ( i*i + j*j < r*r ) {
                    // Get the scale
                    scale = cache[MAX_R+i*MAX_R/(int)r][MAX_R+j*MAX_R/(int)r];
                    
                    // Scale the rgb values
                    new_red   = scale * half_red   / 255 + half_red;
                    new_green = scale * half_green / 255 + half_green;
                    new_blue  = scale * half_blue  / 255 + half_blue;
                    
                    // Construct the new color
                    new_color = ( alpha     << 24 ) | 
                                ( new_red    << 16 ) |
                                ( new_green << 8  ) |
                                ( new_blue        );
                    
                    // Set the pixel            
                    raster.setPixel( new_color, (int)x+i, (int)y+j );
                }
            }
        }
    }

    public void 
    Draw3( Raster raster )
    {
        int r2 = (int) this.r;
        for ( int i=-r2; i<r2; i++ ) {
            for ( int j=-r2; j<r2; j++ ) {
                if ( i*i + j*j < r*r )
                    raster.setPixel( color, (int)x+i, (int)y+j );
            }
        }
    }
}
