package
{
    
    import __AS3__.vec.Vector;
    
    import com.adobe.viewsource.ViewSource;
    
    import flash.display.*;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Vector3D;
    
    import fr.kikko.SystemTracker;

    [SWF(backgroundColor="#010101", frameRate=30)]
    public class LightSphere extends Sprite
    {
        
        private var PI:Number = 3.14159;
        private var TWO_PI:Number;
        private var H_PI:Number;
        private var _container:Shape;
        private var _vertices:Vector.<Number>;
        private var _indices:Vector.<int>;
        private var _vertices3d:Vector.<Number>;
        private var _cols:int;
        private var _rows:int;
        private var _centerZ:Number = 200;
        private var _focalLength:Number = 1000;
        private var _radius:Number = 300;
        private var _offset:Number = 0;
        private var _zoffset:Number = 0;
        private var _useZOffset:Boolean = false;
        private var showMesh:Boolean;
        private var _light:Light3D;
        private var _ui:UI;
        
        public function LightSphere()
        {
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            
            ViewSource.addMenuItem(this, "srcview/index.html");
            
            SystemTracker.init( stage );
            
            _ui = new UI();
            _ui.y = 100;
            addChild( _ui );
            
            init();
        }

        private function init():void
        {
            _vertices = new Vector.<Number>();
            _vertices3d = new Vector.<Number>();
            _indices = new Vector.<int>();
            
            _rows = _cols = 40;
            
            _container = new Shape();
            _container.x = stage.stageWidth >> 1;
            _container.y = stage.stageHeight >> 1;
            addChild( _container );

            showMesh = true;
            
            TWO_PI = PI * 2;
            H_PI = PI * .5;
            
            _light = new Light3D( 1000, 1000, 1000 );
            
            makeTriangles();
            render( null );
            
            addEventListener( Event.ENTER_FRAME, render );
            stage.addEventListener( MouseEvent.MOUSE_WHEEL, changeLightZ );
            stage.addEventListener( MouseEvent.CLICK, toggleMesh );
            stage.addEventListener( KeyboardEvent.KEY_DOWN, toggleZoffset );
        }
        
        private function changeLightZ( e:MouseEvent ):void
        {
            _light.z = ( e.delta > 0 ) ? _light.z - 20: _light.z + 20;
            _ui.setLightZ( _light.z );
        }

        private function toggleMesh( e:MouseEvent ):void
        {
            showMesh = !showMesh;
            _ui.setMesh( showMesh );
        }

        private function toggleZoffset( e:KeyboardEvent ):void
        {
            if ( e.charCode == 122 )
                _useZOffset = true;
            else if ( e.charCode == 97 )
                _useZOffset = false;
        }

        private function render( e:Event ):void
        {
            _container.graphics.clear();
            _offset += .01;
            _zoffset = ( _useZOffset ) ? _zoffset + .01 : 0;
            _vertices.length = 0;
            _vertices3d.length = 0;
            for ( var i:int = 0; i < _rows; ++i )
            {
                for ( var j:int = 0; j < _cols; ++j )
                {
                    var angle:Number = TWO_PI / ( _cols - 1 ) * j + _offset;
                    var angle2:Number = PI * i / ( _rows - 1 ) - H_PI;
                    var cosAngle2:Number = _radius * Math.cos( angle2 );
                    
                    var xpos:Number = Math.cos( angle ) * cosAngle2;
                    var ypos:Number = Math.sin( angle2 + _zoffset ) * _radius;
                    var zpos:Number = Math.sin( angle ) * cosAngle2;
                    var scale:Number = _focalLength / ( _focalLength + zpos + _centerZ );

                    _vertices.push( xpos * scale, ypos * scale );
                    _vertices3d.push( xpos, ypos, zpos );
                }
            }
            
            for ( i = 0; i < _indices.length; i += 3 )
            {
                // backface culling
                var index1:int = _indices[ i ] << 1;
                var index2:int = _indices[ i + 1 ] << 1;
                var index3:int = _indices[ i + 2 ] << 1;
                var ax:Number = _vertices[ index1 ];
                var ay:Number = _vertices[ index1 + 1 ];
                var bx:Number = _vertices[ index2 ];
                var by:Number = _vertices[ index2 + 1 ];
                var cx:Number = _vertices[ index3 ];
                var cy:Number = _vertices[ index3 + 1 ];
                
                // if the triangle is facing the viewer, render it
                if ( (cx - ax) * (by - cy) < (cy - ay) * (bx - cx) )
                {
                    // Light rendering
                    // get normal vector for current triangle
                    // get light dir vector
                    // compute angle between normal and light vectors
                    // use the result to change the color of the triangle
                    
                    /*
                     * Vector math reminder
                     *     - vector length:     |A| = sqrt( a1² + a2² + a3² )
                     *    - vector addition:     A + B = < a1 + b1, a2 + b2, a3 + b3 >
                     *    - dot product:        A.B = a1 * b1 + a2 * b2 + a3 * b3
                     *                        A.B = |A| * |B| * cos( theta )
                     *                        proof:
                     *                            A.A = |A|² * cos( 0 )
                     *                            A.A = |A|² = a1² + a2² + a3²
                     *    - cross product:    http://en.wikipedia.org/wiki/Cross_product
                     */
                    index1 += index1 >> 1;
                    index2 += index2 >> 1;
                    index3 += index3 >> 1;
                    var vecA:Vector3D = new Vector3D( _vertices3d[ index1 ]     - _vertices3d[ index2 ],
                                                      _vertices3d[ index1 + 1 ] - _vertices3d[ index2 + 1 ],
                                                      _vertices3d[ index1 + 2 ] - _vertices3d[ index2 + 2 ] );
                    var vecB:Vector3D = new Vector3D( _vertices3d[ index2 ]     - _vertices3d[ index3 ],
                                                      _vertices3d[ index2 + 1 ] - _vertices3d[ index3 + 1 ],
                                                      _vertices3d[ index2 + 2 ] - _vertices3d[ index3 + 2 ] );

                    var norm:Vector3D = vecA.crossProduct( vecB );

                    _light.x = ( stage.stageWidth >> 1 ) - mouseX;
                    _light.y = ( stage.stageHeight >> 1 ) - mouseY;
                    
                    // calculate the angle between the light vector and the norm
                    // using a dot product
                    var ca:Number = ( norm.x * _light.x + 
                                      norm.y * _light.y + 
                                      norm.z * _light.z ) / ( norm.length * _light.length );

                    var color:uint = 0x5522FF;
                    if ( ca <= 0 ) color = 0;
                    else
                    {
                        color = ( ( color >> 16 & 0xFF ) * ca ) << 16 |
                                ( ( color >> 8 & 0xFF ) * ca ) << 8 |
                                ( color & 0xFF ) * ca;
                    }
                    
                    if ( showMesh )
                        _container.graphics.lineStyle( 1, color );
                    else
                        _container.graphics.beginFill( color );
                        
                    _container.graphics.moveTo( ax, ay );
                    _container.graphics.lineTo( bx, by );
                    _container.graphics.lineTo( cx, cy );
                    _container.graphics.lineTo( ax, ay );
                    //_container.graphics.endFill();
                }
            }
        }

        private function makeTriangles():void
        {
            for ( var i:int = 0; i < _rows; ++i )
            {
                for ( var j:int = 0; j < _cols; ++j )
                {
                    if ( i < _rows - 1 && j < _cols - 1 )
                    {
                        _indices.push( i * _cols + j,
                                       i * _cols + j + 1,
                                       ( i + 1 ) * _cols + j );
                        _indices.push( i * _cols + j + 1,
                                       ( i + 1 ) * _cols + j + 1,
                                       ( i + 1 ) * _cols + j );
                    }
                }
            }
        }
        
    }
    
}