package 
{
    import away3d.containers.View3D;
    import away3d.core.math.Number3D;
    import away3d.events.MouseEvent3D;
    import away3d.materials.BitmapMaterial;
    import away3d.primitives.Sphere;
    import away3d.debug.AwayStats;
    import away3d.core.utils.Cast;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.filters.BitmapFilter;
    import flash.filters.DropShadowFilter;
    import flash.geom.Matrix;
    import flash.display.*;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.text.TextField;
    import flash.text.TextFormat;
    
    /**
     * ...
     * @author Tony Lukasavage - SavageLook.com
     */
    [SWF("#000000", height="600", width="800", frameRate="60")]
    public class reflect_away3d extends Sprite 
    {
        private var _view:View3D;
        private var _matrix:Matrix = new Matrix();
        private var _bitmapReflect:Bitmap = new Bitmap();
        private var _bitmapReflectGradient:Sprite = new Sprite();
        private var _bmd:BitmapData;
        private var _sphere:Sphere;
        private var _titleText:TextField;
        private var _drawn:DisplayObject;
        private var _bounds:Rectangle;
        
        private var _yscale:Number = 1;
        private var _xscale:Number = 1;
        private var _rotX:Number = 0;
        private var _rotY:Number = 0;
        private var _overSphere:Boolean = false;
        
        /*
        * You can calculate the distance to the very bottom of your target object to determine 
        * where the reflection should be placed by replacing this line in _onEnterFrame():
        * 
        * _bitmapReflect.y = stage.stageHeight / 2 + _distance;
        * 
        * with this line:
        * 
        * _bitmapReflect.y = stage.stageHeight / 2 + drawn.height / 2;
        * 
        * The problem is that if your target object's height changes due to rotation, scaling, 
        * translation, etc... the position of the reflection will change.  In short, use a 
        * first line above for dynamic target objects, use the second for static target objects.
        */
        private var _distance:Number = 92;
        
        [Embed( "earth.jpg")] private var earthImage:Class;
        
        public function reflect_away3d():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            
            // configure stage
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.quality = StageQuality.HIGH;
            
            // create away3d view 
            _view = new View3D();
            _view.x = stage.stageWidth / 2;
            _view.y = stage.stageHeight / 2;
            _view.camera.position = new Number3D(0, 0, -1000);
            _view.camera.lookAt(new Number3D(0, 0, 0));
            
            // add away3d object(s)
            _sphere = new Sphere( { material:new BitmapMaterial(Cast.bitmap(earthImage), { smooth:true } ), 
                segmentsH:20, segmentsW:20 } );
            _sphere.useHandCursor = true;
            _view.scene.addChild(_sphere);
            
            // configure reflection display objects
            _bitmapReflectGradient.cacheAsBitmap = true;
            _bitmapReflect.cacheAsBitmap = true;
            _bitmapReflect.mask = _bitmapReflectGradient;
            
            // create demo title
            _titleText = new TextField();
            _titleText.text = "SavageLook.com -- Reflection in Away3D";
            _titleText.setTextFormat(new TextFormat("arial", 14, 0xffffff, false, false, false, "https://savagelook.com/blog/away3d/reflection-in-away3d"));
            _titleText.x = 10;
            _titleText.y = stage.stageHeight - 30;
            _titleText.width = _titleText.textWidth * 1.1;
            _titleText.filters = [new DropShadowFilter(3,45,0xffffff,1,4,4)];
            
            // add objects
            this.addChild(_view);
            this.addChild(new AwayStats());
            this.addChild(_bitmapReflect);
            this.addChild(_bitmapReflectGradient);
            this.addChild(_titleText);
            
            // add render loop
            this.addEventListener(Event.ENTER_FRAME, _onEnterFrame);
            stage.addEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
            stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void { _overSphere = false; } );
            _sphere.addEventListener(MouseEvent3D.MOUSE_DOWN, function(e:MouseEvent3D):void { _overSphere = true; } );
        }
        
        private function _onMouseMove(e:MouseEvent):void {
            if (!e.buttonDown) {
                _rotX = ((stage.stageWidth / 2) - e.stageX) / (stage.stageWidth / 8);
                _rotY = ((stage.stageHeight / 2) - e.stageY) / (stage.stageHeight / 8);
            } else {
                if (_overSphere) {
                    _rotX = 0;
                    _rotY = 0;
                    if (e.stageX > _distance && e.stageX < stage.stageWidth - _distance) { 
                        _view.x = e.stageX;
                    }
                    if (e.stageY < stage.stageHeight / 2 && e.stageY > _distance) {
                        _view.y = e.stageY;
                    }
                }
            }
        }
        
        private function _onEnterFrame(e:Event):void {
            _sphere.rotationX -= _rotY;
            _sphere.rotationY += _rotX;
            _sphere.applyRotations();
            _xscale = _yscale = 1 - ((300 - _view.y) / 600); 
            _view.render();
            
            // find 2d bounding box of sphere
            _drawn = (_view.session.getContainer(_view) as DisplayObjectContainer).getChildAt(0);
            _bounds = _drawn.getRect(this);
            
            // redraw reflection
            _bmd = new BitmapData(_drawn.width, _drawn.height, true, 0x00ffffff);
            _matrix.createBox(_xscale, -_yscale, 0, _drawn.width/2, _drawn.height * _yscale / 2);
            _bmd.draw(_drawn, _matrix);
            _bitmapReflect.bitmapData = _bmd;
            _bitmapReflect.x = _bounds.x;
            _bitmapReflect.y = stage.stageHeight / 2 + _distance;
            
            // redraw gradient mask for reflection
            _matrix.createGradientBox(_bitmapReflect.width, _bitmapReflect.height * _yscale / 2, Math.PI / 2, 0, 0);
            _bitmapReflectGradient.graphics.clear();
            _bitmapReflectGradient.graphics.beginGradientFill("linear", [0xffffff, 0xffffff], [0.9, 0], [0, 255], _matrix);
            _bitmapReflectGradient.graphics.drawRect(0, 0, _bitmapReflect.width, _bitmapReflect.height);
            _bitmapReflectGradient.graphics.endFill();
            _bitmapReflectGradient.x = _bitmapReflect.x;
            _bitmapReflectGradient.y = _bitmapReflect.y;
        }
    }
}