Droste / Escher Effect in AGAL

One last bonus post for the new year, seeing as how I remembered I had a blog. This is really just a snippet I would ordinarily post on Wonderfl, but Wonderfl seems not to be working for me lately (if anyone wants to check out my Wonderfl profile and let me know if they can view any of my posted codes, I’d appreciate it. But I can’t).

[kml_flashembed publishmethod=”static” wmode=”direct” fversion=”18.0.0″ movie=”https://blog.onebyonedesign.com/wp-content/uploads/2015/12/droste.swf” width=”465″ height=”465″ targetclass=”flashmovie”]

Get Adobe Flash player

[/kml_flashembed]

/**
 *	Copyright (c) 2016 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

package 
{
    
import com.adobe.utils.AGALMiniAssembler;
import com.bit101.components.CheckBox;
import com.bit101.components.HUISlider;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display3D.Context3D;
import flash.display3D.Context3DProfile;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
import flash.display3D.Context3DTextureFormat;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.textures.Texture;
import flash.display3D.VertexBuffer3D;
import flash.events.Event;
import flash.geom.Matrix3D;
import flash.net.URLRequest;
import flash.system.LoaderContext;

/**
 * Zooming Droste effect 
 * largely ported from http://codepen.io/spleen666/pen/zbhcK
 * @author Devon O.
 */

[SWF(width='465', height='465', backgroundColor='#000000', frameRate='60')]
public class Main extends Sprite 
{
 
    private static var VERTEX_SHADER:String =
    
    
    ]]>;
    
    private var context:Context3D;
    private var isReady:Boolean;
    private var program:Program3D;
    private var renderMatrix:Matrix3D;
    private var vertexBuffer:VertexBuffer3D;
    private var indexBuffer:IndexBuffer3D;
    private var textureData:BitmapData;
    private var texture:Texture;
    private var textureWidth:int;
    private var textureHeight:int;
    
    private var zoomControl:HUISlider;
    private var autoZoom:Boolean=true;
    
    public function Main():void 
    {
        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);
    }
    
    private function init(e:Event = null):void 
    {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        
        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align = StageAlign.TOP_LEFT;
        this.isReady = false;
        this.renderMatrix = new Matrix3D();
        
        loadImage();
        startRender();
    }
    
    private function loadImage():void 
    {
        var l:Loader = new Loader();
        l.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoad);
        var path:String = "http://assets.wonderfl.net/images/related_images/1/12/1247/124761a085cc7a1704c9fb601fe9d35e2e5a8b2e";
        l.load(new URLRequest(path), new LoaderContext(true));
    }
    
    private function onImageLoad(event:Event=null):void
    {
        event.currentTarget.removeEventListener(Event.COMPLETE, onImageLoad);
        var l:Loader = (event.currentTarget as LoaderInfo).loader;
        this.textureData = (l.content as Bitmap).bitmapData;    
        
        requestContext();
    }
    
    private function startRender():void
    {
        addEventListener(Event.ENTER_FRAME, onFrame);
    }
    
    private function requestContext():void
    {
        stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onStage3dContext);
        stage.stage3Ds[0].requestContext3D(Context3DRenderMode.AUTO, Context3DProfile.BASELINE);
    }
    
    private function onStage3dContext(e:Event):void
    {
        this.context = stage.stage3Ds[0].context3D;
        this.context.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2, false, false);
        
        createVertexBuffer();
        createIndexBuffer();
        createTextures();
        createPrograms();
        createControls();
        
        this.isReady = true;
    }
    
    private function createVertexBuffer():void
    {
        var vertices:Vector. = new [
        //    x            y        u  v
            -1.0,         1.0,      0, 0, 
             1.0,         1.0,      1, 0,
            -1.0,        -1.0,      0, 1,
             1.0,        -1.0,      1, 1  ];
            
        this.vertexBuffer = this.context.createVertexBuffer(4, 4);
        this.vertexBuffer.uploadFromVector(vertices, 0, 4);
    }
    
    private function createIndexBuffer():void
    {
        this.indexBuffer = this.context.createIndexBuffer(6);
        
       // 2 triangles (0, 1, 2) & (1, 3, 2)
       //   0 - 1
       //   | / |
       //   2 - 3
        this.indexBuffer.uploadFromVector(new [0, 1, 2,   1, 3, 2], 0, 6);
    }
    
    private function createTextures():void
    {
        this.textureWidth = this.textureData.width;
        this.textureHeight = this.textureData.height;
        this.texture = this.context.createTexture(this.textureWidth, this.textureHeight, Context3DTextureFormat.BGRA, false);
        this.texture.uploadFromBitmapData(this.textureData);
        this.textureData.dispose();
    }

    private function createPrograms():void
    {
        var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
        vertexShaderAssembler.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER);
        
        var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
        fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER);
        
        this.program = this.context.createProgram();
        this.program.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
    }
    
    private function createControls():void
    {
        var s1:HUISlider = new HUISlider(this, 5, 5, "Radius1", function(e:Event):void { radius1 = s1.value; } );
        s1.minimum = -10.0;
        s1.maximum = 10.0;
        s1.tick = 1.0;
        s1.value = -1.0;
        
        var s2:HUISlider = new HUISlider(this, 5, 20, "Radius2", function(e:Event):void { radius2 = s2.value; } );
        s2.minimum = -10.0;
        s2.maximum = 10.0;
        s2.tick = 1.0;
        s2.value = -2.0;
        
        var s3:HUISlider = new HUISlider(this, 5, 35, "Center X", function(e:Event):void { centerX = s3.value; } );
        s3.minimum = 0.0;
        s3.maximum = 1.0;
        s3.tick = .025;
        s3.value = 0.50;
        
        var s4:HUISlider = new HUISlider(this, 5, 50, "Center Y", function(e:Event):void { centerY = s4.value; } );
        s4.minimum = 0.0;
        s4.maximum = 1.0;
        s4.tick = .025;
        s4.value = .50;
        
        var s5:HUISlider = new HUISlider(this, 5, 65, "Scale", function(e:Event):void { _scale = s5.value; } );
        s5.minimum = 10.0;
        s5.maximum = 256.0;
        s5.tick = 1.0;
        s5.value = 128;
        
        this.zoomControl = new HUISlider(this, 5, 80, "Zoom", function(e:Event):void { zoom = zoomControl.value; } );
        zoomControl.minimum = 0.0;
        zoomControl.maximum = 10;
        zoomControl.tick = .025;
        
        var cb:CheckBox = new CheckBox(this, 5, 95, "Auto Zoom", function(e:Event):void { autoZoom = cb.selected; } );
        cb.selected = true;
    }
    
    private var radius1:Number = -2.0;
    private var radius2:Number = 5.0;
    
    private var centerX:Number = 0.50;
    private var centerY:Number = 0.50;
    
    private var zoom:Number = 0.0;
    private var _scale:Number = 256.0;

    private var fc0:Vector. = new [1.0, 0.0, Math.PI, 2*Math.PI];
    private var fc1:Vector. = new [1e-10, Math.PI/2, 0.0, 1.0];
    private var fc2:Vector. = new [0.0, 0.0, .50, -.50];
    private var fc3:Vector. = new [0.0, 0.0, 0.0, 0.0];
    
    private function onFrame(e:Event):void
    {
        if (!this.isReady)
            return;
            
        this.context.clear(0, 0, 0, 1);
        
        this.renderMatrix.identity();
        this.renderMatrix.appendScale(this.textureWidth / stage.stageWidth, this.textureHeight / stage.stageHeight, 1.0);
        this.context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, this.renderMatrix, true);
        
        this.context.setVertexBufferAt(0, this.vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2);
        this.context.setVertexBufferAt(1, this.vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_2);
        
        fc2[0] = -centerX;
        fc2[1] = -centerY;
        
        fc3[0] = zoom;
        fc3[1] = _scale;
        fc3[2] = radius1;
        fc3[3] = radius2;
        
        if (this.autoZoom)
        {
            this.zoomControl.value += this.zoomControl.tick;
            if (this.zoomControl.value >= this.zoomControl.maximum)
            {
                this.zoomControl.value = 0.0;
            }
        }
        
        this.context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, this.fc0, 1);
        this.context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, this.fc1, 1);
        this.context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, this.fc2, 1);
        this.context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, this.fc3, 1);
        
        this.context.setTextureAt(0, this.texture);
        
        this.context.setProgram(this.program);
        
        this.context.drawTriangles(this.indexBuffer);
        
        this.context.present();
    }
    
}
    
}

Happy New Year!