In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

ClipFragmentShaderTest

This is a custom standard material to help perform simple clipping of fragments with a SINGLE draw-call  WITHOUT having to use stencil buffer or a render-to-texture pre-pass. Before rendering, the necessary planes in local coordinate spaces are pre-calculated.

This allows stuffs like mirrors, alternate dimensions, portals, etc. to be drawn. For convex portals, you just need multiple versions of the shader program to test against multiple planes, depending on how many portal sides there are.

Drag the slider to see the 2 different sides of the cube.

A WebGL example of this can be found at: http://thecarpandtheseagull.thecreatorsproject.com , but I'm not sure if their approach is similar or different from mine.
Get Adobe Flash player
by Glidias 11 Jan 2013
/**
 * Copyright Glidias ( http://wonderfl.net/user/Glidias )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/gDWT
 */

package 
{
    
    import alternativa.engine3d.core.Resource;
    import com.bit101.components.HUISlider;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Vector3D;
    /**
     * ...
     * @author Glenn Ko
     */
    public class ClipFragmentShaderTest extends Sprite {
    
        public function ClipFragmentShaderTest() {
            
            addChild( new MyClipTemplate() );
        }
    }
}

    //package engine3d.templates 
//{
    import alternativa.engine3d.controllers.SimpleObjectController;
    import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.core.events.MouseEvent3D;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.core.Resource;
    import alternativa.engine3d.core.View;
    import alternativa.engine3d.lights.AmbientLight;
    import alternativa.engine3d.lights.DirectionalLight;
    import flash.display.Sprite;
    import flash.display.Stage3D;
    import flash.display.StageAlign;
    import flash.display.StageQuality;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    /**
     * ...
     * @author Glenn Ko
     */
    //public
    class Template extends Sprite {
        private var _viewHeight:Number;
        private var _viewWidth:Number;
        
        public static const VIEW_CREATE:String = 'view_create'
        
        protected var stage3D:Stage3D
        protected var camera:Camera3D
        public var scene:Object3D
        public var cameraController:SimpleObjectController;
        public var objectController:SimpleObjectController;
        public var controlObject:Object3D;
        
        protected var directionalLight:DirectionalLight;
        protected var ambientLight:AmbientLight;    
        
        public var renderId:int = 0;
        
        public function Template(viewWidth:Number=0, viewHeight:Number=0) {
            this._viewWidth = viewWidth;
            this._viewHeight = viewHeight;
            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;
            stage.quality = StageQuality.HIGH;            
            
            
            //Stage3Dを用意
            stage3D = stage.stage3Ds[0];
            //Context3Dの生成、呼び出し、初期化
            stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
            stage3D.requestContext3D();
        }
        
        private function onStageResize(e:Event=null):void 
        {
            var w:Number;
            var h:Number;
            var sh:Number = stage.stageHeight;
            var sw:Number = stage.stageWidth;
            camera.view.width = w=  viewWidth != 0 ? viewWidth : sw;
            camera.view.height = h = viewHeight != 0 ? viewHeight : sh;
            camera.view.x = (sw - w) * .5;
            camera.view.y = (sh - h) * .5;
        }
        
        
        private function onContextCreate(e:Event):void {
            stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
            //View3D(表示エリア)の作成
            var view:View = new View(viewWidth!= 0 ? viewWidth : stage.stageWidth, viewHeight!=0 ? viewHeight : stage.stageHeight);
            view.antiAlias = 4
            addChild(view);
            stage.addEventListener(Event.RESIZE, onStageResize);
            
            //Scene(コンテナ)の作成
            scene = new Object3D();

            //Camera(カメラ)の作成
            camera = new Camera3D(1, 100000);
            camera.view = view;
            scene.addChild(camera)
            camera.diagram
            addChild(camera.diagram)
            
            //Cameraをコントロールする場合は、CameraControlerの作成
            cameraController = new SimpleObjectController(stage, camera, 100);
            cameraController.mouseSensitivity = 0;
            cameraController.unbindAll();
            
            //Cameraの位置調整
            cameraController.setObjectPosXYZ(0, -300, 0);
            cameraController.lookAtXYZ(0, 0, 0);
            
            //Lightを追加
            ambientLight = new AmbientLight(0xFFFFFF);
            ambientLight.intensity = 0.5;
            scene.addChild(ambientLight);
            
            //Lightを追加
            directionalLight = new DirectionalLight(0xFFFFFF);
            //手前右上から中央へ向けた指向性light
            directionalLight.x = 0;
            directionalLight.y = -100;
            directionalLight.z = -100;
            directionalLight.lookAt(0, 0, 0);
            scene.addChild(directionalLight);
            //directionalLight.visible = false;
            
        
            //コントロールオブジェクトの作成
            controlObject = new Object3D();
            scene.addChild(controlObject);
            
                //オブジェクト用のコントローラー(マウス操作)
            objectController = new SimpleObjectController(stage, controlObject, 100);
            objectController.mouseSensitivity = 0.2;
            
            dispatchEvent(new Event(VIEW_CREATE));
        }
        
        
        
        
        public function initialize():void {
            for each (var resource:Resource in scene.getResources(true)) {
                //trace(resource)
                resource.upload(stage3D.context3D);
            }
            

            //レンダリング
            camera.render(stage3D);
            
            addEventListener(Event.ENTER_FRAME, onRenderTick);
        }

        
        public function onRenderTick(e:Event):void {
            objectController.update();
            renderId++;
            camera.render(stage3D);
            
        }
        
        public function get viewHeight():Number 
        {
            return _viewHeight;
        }
        
        public function set viewHeight(value:Number):void 
        {
            _viewHeight = value;
            onStageResize();
        }
        
        public function get viewWidth():Number 
        {
            return _viewWidth;
        }
        
        public function set viewWidth(value:Number):void 
        {
            _viewWidth = value;
            onStageResize();
        }
    
    }
//}


import com.bit101.components.HUISlider;

class MyClipTemplate extends Template 
    {
        private var _faceMask:FaceMask2 = new FaceMask2();
        private var uiRotZ:HUISlider;
        
        public function MyClipTemplate() 
        {
              addEventListener(Template.VIEW_CREATE, init);
        }
        
        private function init(e:Event):void 
        {
            // setup 3d
            scene.addChild(_faceMask.obj);
            
            
            
            // setup 2d UI
            uiRotZ = new HUISlider(this, 10, 10, "Drag slider", onRotValueDrag);
            uiRotZ.minimum = -Math.PI * .25;
            uiRotZ.maximum = Math.PI * .5;
            
            
            
            // STANDARD setup
            for each (var resource:Resource in scene.getResources(true)) {
                //trace(resource)
                resource.upload(stage3D.context3D);
            }
            

            
            camera.y-=80;
            //レンダリング
            camera.render(stage3D);

            
            addEventListener(Event.ENTER_FRAME, onRenderTick);
            
        }
        
        private function onRotValueDrag(e:Event):void 
        {
            _faceMask.obj.rotationZ = uiRotZ.value;
        }
        
        override public function onRenderTick(e:Event):void {
    
            
            objectController.update();
            

            _faceMask.planeCut.calculatePlane( new Vector3D(camera.x, camera.y, camera.z) );
            _faceMask.planeCut2.setReverseResultOf(_faceMask.planeCut);
            camera.render(stage3D);
        
        }
        
    }

     class PlaneResult 
    {
        public var v:Vector3D = new Vector3D();  // the result object reference
        
        public function PlaneResult() 
        {
            
        }
        
        public function setReverseResultOf(other:PlaneCut2Points):void {
            v.x = -other.v.x;
            v.y = -other.v.y;
            v.z = -other.v.z;
            v.w = -other.v.w;
        }
        public function clone():PlaneResult {
            return null;    
        }
    }

    import alternativa.engine3d.core.Object3D;
    import flash.geom.Vector3D;
    /**
     * ...
     * @author Glenn Ko
     */
    //public 
    class PlaneCut2Points extends PlaneResult
    {
        public var c1:Vector3D = new Vector3D();
        public var c2:Vector3D = new Vector3D();
        
        private static const VC:Vector3D = new Vector3D();
        
        
        public var coordinateSpace:Object3D;
        
        public function reverse():void {
            var temp:Vector3D = c1;
            c1 = c2;
            c1 = temp;
        }
        
        public function PlaneCut2Points(coordinateSpace:Object3D) 
        {
            this.coordinateSpace = coordinateSpace;
        }
        
            
        /**
         * Important: Use one of the below calcuations methods to calculate plane before rendering!! 
         */
        public function calculatePlane(camPosGlobalSpace:Vector3D ):void {
            var vc:Vector3D = coordinateSpace.globalToLocal( camPosGlobalSpace );
            var v1:Vector3D =  c1.subtract(vc);
            var v2:Vector3D =  c2.subtract(vc);
            
            v = v1.crossProduct(v2);
            v.normalize();
            v.w = vc.dotProduct(v);    
        }
        
        
        
        // --------------
        
        override public function clone():PlaneResult {
            var me:PlaneCut2Points = new PlaneCut2Points(coordinateSpace);
            me.c1 = c1.clone();
            me.c2 = c2.clone();
            me.v = v.clone();
            return me;
        }
        
    }
    
    
    class PlaneCut3Points extends PlaneResult
    {
        
        // points of triangle in counter-clockwise direction for plane direction
        public var c1:Vector3D = new Vector3D();
        public var c2:Vector3D = new Vector3D();
        public var c3:Vector3D = new Vector3D();
        
        private static const VC:Vector3D = new Vector3D();
        
        public var coordinateSpace:Object3D;
        
        public function PlaneCut3Points(coordinateSpace:Object3D) 
        {
            this.coordinateSpace = coordinateSpace;
        }
        
        public function reverse():void {
            var temp:Vector3D = c1;
            c1 = c3;
            c3 = temp;
        }
        
            
        /**
         * Important: Use one of the below calcuations methods to calculate plane before rendering!! 
         */
        public function calculatePlaneFor(objSpace:Object3D):void {
        
            var v1:Vector3D =  objSpace.globalToLocal( coordinateSpace.localToGlobal(c1) );
            var v2:Vector3D =  objSpace.globalToLocal( coordinateSpace.localToGlobal(c2) );
            var v3:Vector3D =   objSpace.globalToLocal( coordinateSpace.localToGlobal(c3) );
            
            var v12:Vector3D = v2.subtract(v1);
            var v13:Vector3D = v3.subtract(v1);
            v = v12.crossProduct(v13);
            v.normalize();
            v.w = v1.dotProduct(v);    
        }
        
        
        // --------------
        
        override public function clone():PlaneResult {
            var me:PlaneCut3Points = new PlaneCut3Points(coordinateSpace);
            me.c1 = c1.clone();
            me.c2 = c2.clone();
            me.c3 = c3.clone();
            me.v = v.clone();
            return me;
        }
    }
    





    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.materials.FillMaterial;
    import alternativa.engine3d.materials.Material;
    import alternativa.engine3d.materials.StandardMaterial;
    import alternativa.engine3d.objects.Mesh;
    import alternativa.engine3d.primitives.Box;
    import alternativa.engine3d.primitives.Plane;
    import alternativa.engine3d.resources.BitmapTextureResource;

    import flash.geom.Matrix3D;
    
    import flash.display.BitmapData;
    import flash.events.Event;
    import flash.events.MouseEvent;
    
    import alternativa.engine3d.alternativa3d;
    use namespace alternativa3d;
    /**
     * ...
     * @author Glenn Ko
     */
    //public 
    class FaceMask2 
    {
        public var obj:Object3D;
        static public const WIDTH:Number = 170;
        static public const HEIGHT:Number = 170;
        

        // markers to show 2-cut points
        private var c:Object3D;
        private var c2:Object3D;
        
        // the plane cut references
        public var planeCut:PlaneCut2Points;
        public var planeCut2:PlaneCut2Points;
        
        
        
        public function FaceMask2() 
        {
             //Textureの作成
            
            var diffuseMap:BitmapData = new BitmapData(16, 16, false, 0xAA6666);

                 var perlinBitmapData:BitmapData = new BitmapData(128, 128, false, 0);
             perlinBitmapData.perlinNoise(256, 256, 4, Math.random() * 99999999, false, false, 7, true);
            
              
              var conv:PlanarDispToNormConverter = new PlanarDispToNormConverter();
              conv.setDisplacementMapData(perlinBitmapData);
           var normalMap:BitmapData = conv.convertToNormalMap().bitmapData;
           
             var material:PlaneCutMaterial = new PlaneCutMaterial( new BitmapTextureResource( diffuseMap), new BitmapTextureResource( normalMap), null, null, null  );
    //    material.glossiness = 0;// .1;
    material.specularPower = 0;
        material.alphaThreshold = 1;
            
            
             var plane:Plane = new Plane(WIDTH*.5, HEIGHT*.5, 16, 16, false, false, material, material);
              GeometryTools.displaceGeometry(perlinBitmapData, plane.geometry, 22);
              GeometryTools.smoothShading(plane);
              
        
    
        
            obj = new Object3D();
                 obj.rotationX =  Math.PI * .5;
            obj.addChild(plane);
            // obj = plane;
            
             
             obj.addChild( c = new Box(200, 200, 200, 1, 1, 1, false, new FillMaterial(0xFF0000, .2) ) );
                     c.mouseChildren = false;
            c.mouseEnabled = false;
            
             c = obj.addChild( new Box(10, 10, 10, 1, 1, 1, false, new FillMaterial(0x00FF00, 1) ) );
             c.x = -100;
            c.z = 100;
            c.y = -100;
            
            
             c2 = obj.addChild( new Box(10, 10, 10, 1, 1, 1, false, new FillMaterial(0x0000FF, 1) ) );
             c2.x = -100;
            c2.z = 100;
            c2.y = 100;
            // obj = plane;
            
            planeCut = new PlaneCut2Points(obj);
            
            planeCut.c1.x = c.x;
            planeCut.c1.y = c.y;
            planeCut.c1.z = c.z;
            
            planeCut.c2.x = c2.x;
            planeCut.c2.y = c2.y;
            planeCut.c2.z = c2.z;
            material.planeResult = planeCut;
            
            
            // test VERSIOn 2 of environment!!
            var plane2:Mesh = new Plane(WIDTH, HEIGHT, 16, 16, false, false, material, material);
            perlinBitmapData = new BitmapData(256, 256, false, 0);// perlinBitmapData.clone();
             perlinBitmapData.perlinNoise(256, 256, 8, Math.random() * 99999999, false, false, 7, true);
              GeometryTools.displaceGeometry(perlinBitmapData, plane2.geometry, 28);
              GeometryTools.smoothShading(plane2);
              conv.setDisplacementMapData(perlinBitmapData);
            var mat2:PlaneCutMaterial;
            
            plane2.setMaterialToAllSurfaces( mat2 = material.clone() as PlaneCutMaterial );
            mat2.normalMap = new BitmapTextureResource( conv.convertToNormalMap().bitmapData );
            planeCut2 = planeCut.clone() as PlaneCut2Points;
            planeCut2.reverse();
            mat2.planeResult = planeCut2;
            mat2.diffuseMap = new BitmapTextureResource( new BitmapData(16, 16, false, 0x22FF40) );
            obj.addChild(plane2);
            
            
            var child:Object3D;
            
            child = obj.addChild(  new Box(40, 40, 180, 1, 1, 1, false, mat2) );
            child.rotationZ = Math.PI*.25;
            child.composeTransforms();
            GeometryTools.transformGeometry( (child as Mesh).geometry, child.transform );
            GeometryTools.resetEuler(child);
            
        
            child = obj.addChild( new Box(40, 40, 180, 1, 1, 1, false, material) );
            child.rotationX = Math.PI*.25;
            child.composeTransforms();
            GeometryTools.transformGeometry( (child as Mesh).geometry, child.transform );
            GeometryTools.resetEuler(child);
            child.useHandCursor = true;
            child.mouseChildren = false;
            child.mouseEnabled = true;
        //    child.mouseHandlingType
        
        
            
            
        }
        
    
        
    }
    
    



//package com.tartiflop {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.geom.Vector3D;
    
    
    /**
     * "Abstract" class for both planar and spherical converters. 
     */
    //public
    class DispToNormConverter {
        
        
        protected var direction:String = "y";
        protected var amplitude:Number = 8;
        protected var normalMap:Bitmap;
        
        protected var displcamentBitmapData:BitmapData;
        
        public function DispToNormConverter() {
        }

        /**
         * Sets the initial displacement map data.
         */
        public function setDisplacementMapData(displcamentBitmapData:BitmapData):void {
            this.displcamentBitmapData = displcamentBitmapData;                         
        }

        /**
         * Sets the direction of the displacement ("x", "y" or "z").
         */
        public function setDirection(direction:String):void {
            this.direction = direction;
        }
        
        /**
         * Sets the amplitude factor so that the displacements can be more or less pronounced. 
         */
        public function setAmplitude(amplitude:Number):void {
            this.amplitude = amplitude;
        }

        /**
         * Returns the calculated normal map bitmap data.
         */
        public function getNormalMap():Bitmap {
            return normalMap;
        }

        
        /**
         * Converts a pixel value into a displacement between 0 and 1. Assumes greyscale data so only uses the blue channel.
         */
        protected function getDisplacement(displcamentBitmapData:BitmapData, x:int, y:int):Number {
            return (displcamentBitmapData.getPixel(x, y) & 0xFF) / 255.0;
        }

        /**
         * Converts the calculated normal vector into RGB values and sets the pixel value. 
         * Takes into account the direction of the displacements/phi direction.
         */
        protected function setNormal(normalBitmapData:BitmapData, x:int, y:int, normal:Vector3D):void {
            normal.normalize();
            
            
            if (direction == "x") {
                var nx:Number = (normal.z / 2) + 0.5;
                var ny:Number = (normal.x / 2) + 0.5;
                var nz:Number = (normal.y / 2) + 0.5;
            
            } else if (direction == "y") {
                nx = (normal.x / 2) + 0.5;
                ny = (normal.z / 2) + 0.5;
                nz = (normal.y / 2) + 0.5;
                
            } else {
                nx = (normal.x / 2) + 0.5;
                ny = (normal.y / 2) + 0.5;
                nz = (normal.z / 2) + 0.5;
                
            }
            var color:int = nx*0xFF << 16 | ny*0xFF << 8 | nz*0xFF;
            
            normalBitmapData.setPixel(x, y, color);    
        }
        
        public function convertToNormalMap():Bitmap {
            return null;
        }

    }
//}

//package com.tartiflop {

    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.geom.Vector3D;

    /**
     * Converts displacement map data into normal map data. 
     * Assumes that the displacement map data is
     * greyscale. Assumes also that the bitmap data is for a single plane.
     *  
     * Normal map is calculated from the surface gradients using first (centered) and 
     * second order (forward and backward) finite differences.
     * 
     * Direction of the displacement can be chosen to produce normal maps for xy, yz and zx planes.
     */
    //public 
    class PlanarDispToNormConverter extends DispToNormConverter {

        /**
         * Converts the displacement map to a normal map.
         */
        public override function convertToNormalMap():Bitmap {
            
            if (displcamentBitmapData == null) {
                return new Bitmap();
            }
            
            var width:Number = displcamentBitmapData.width;
            var height:Number = displcamentBitmapData.height;
            
            var normalBitmapData:BitmapData = new BitmapData(width, height, false, 0x000000);

            var nz:Number = 1. / amplitude;

            // calculate the normals over the central region (first order centered finite difference scheme).
            for (var i:Number = 1; i < width - 1; i++) {
                for (var j:Number = 1; j < height - 1; j++) {
                    var nx:Number = -0.5 * (getDisplacement(displcamentBitmapData, i+1, j  ) - getDisplacement(displcamentBitmapData, i-1, j  ));
                    var ny:Number = -0.5 * (getDisplacement(displcamentBitmapData, i  , j+1) - getDisplacement(displcamentBitmapData, i  , j-1));
                    
                    setNormal(normalBitmapData, i, j, new Vector3D(nx, ny, nz));
                }
            }

            // calculate the normals over the top and bottom edges (second order forward and backward finite difference schemes and first order centered)
            for (i = 1; i < width - 1; i++) {
                nx = -0.5 * (getDisplacement(displcamentBitmapData, i+1, 0) - getDisplacement(displcamentBitmapData, i-1, 0));
                ny = -0.333333 * (-3* getDisplacement(displcamentBitmapData, i, 0) + 4* getDisplacement(displcamentBitmapData, i, 1) - getDisplacement(displcamentBitmapData, i, 2));
                
                setNormal(normalBitmapData, i, 0, new Vector3D(nx, ny, nz));
                
                nx = -0.5 * (getDisplacement(displcamentBitmapData, i+1, height-1) - getDisplacement(displcamentBitmapData, i-1, height-1));
                ny = 0.333333 * (-3* getDisplacement(displcamentBitmapData, i, height-1) + 4* getDisplacement(displcamentBitmapData, i, height-2) - getDisplacement(displcamentBitmapData, i, height-3));
                
                setNormal(normalBitmapData, i, height-1, new Vector3D(nx, ny, nz));
            }

            // calculate the normals over the left and right edges (second order forward and backward finite difference schemes and first order centered)
            for (j = 1; j < height - 1; j++) {

                nx = -0.333333 * (-3* getDisplacement(displcamentBitmapData, 0, j) + 4* getDisplacement(displcamentBitmapData, 1, j) - getDisplacement(displcamentBitmapData, 2, 1));
                ny = -0.5 * (getDisplacement(displcamentBitmapData, 0, j+1) - getDisplacement(displcamentBitmapData, 0, j-1));
                setNormal(normalBitmapData, 0, j, new Vector3D(nx, ny, nz));

                nx = 0.333333 * (-3* getDisplacement(displcamentBitmapData, width-1, j) + 4* getDisplacement(displcamentBitmapData, width-2, j) - getDisplacement(displcamentBitmapData, width-3, 1));
                ny = -0.5 * (getDisplacement(displcamentBitmapData, width-1, j+1) - getDisplacement(displcamentBitmapData, width-1, j-1));
                setNormal(normalBitmapData, width-1, j, new Vector3D(nx, ny, nz));
            }

            // calculate the normals at the orners (second order forward and backward finite difference schemes)
            nx = -0.333333 * (-3* getDisplacement(displcamentBitmapData, 0, 0) + 4* getDisplacement(displcamentBitmapData, 1, 0) - getDisplacement(displcamentBitmapData, 2, 0));
            ny = -0.333333 * (-3* getDisplacement(displcamentBitmapData, 0, 0) + 4* getDisplacement(displcamentBitmapData, 0, 1) - getDisplacement(displcamentBitmapData, 0, 2));
            setNormal(normalBitmapData, 0, 0, new Vector3D(nx, ny, nz));
                
            nx = -0.333333 * (-3* getDisplacement(displcamentBitmapData, 0, height-1) + 4* getDisplacement(displcamentBitmapData, 1, height-1) - getDisplacement(displcamentBitmapData, 2, height-1));
            ny =  0.333333 * (-3* getDisplacement(displcamentBitmapData, 0, height-1) + 4* getDisplacement(displcamentBitmapData, 0, height-2) - getDisplacement(displcamentBitmapData, 0, height-3));
            setNormal(normalBitmapData, 0, height-1, new Vector3D(nx, ny, nz));
                
            nx =  0.333333 * (-3* getDisplacement(displcamentBitmapData, width-1, 0) + 4* getDisplacement(displcamentBitmapData, width-2, 0) - getDisplacement(displcamentBitmapData, width-3, 0));
            ny = -0.333333 * (-3* getDisplacement(displcamentBitmapData, width-1, 0) + 4* getDisplacement(displcamentBitmapData, width-1, 1) - getDisplacement(displcamentBitmapData, width-1, 2));
            setNormal(normalBitmapData, width-1, 0, new Vector3D(nx, ny, nz));
                
            nx =  0.333333 * (-3* getDisplacement(displcamentBitmapData, width-1, height-1) + 4* getDisplacement(displcamentBitmapData, width-2, height-1) - getDisplacement(displcamentBitmapData, width-3, height-1));
            ny =  0.333333 * (-3* getDisplacement(displcamentBitmapData, width-1, height-1) + 4* getDisplacement(displcamentBitmapData, width-1, height-2) - getDisplacement(displcamentBitmapData, width-1, height-3));
            setNormal(normalBitmapData, width-1, height-1, new Vector3D(nx, ny, nz));
                
            // Create the normal map
            normalMap = new Bitmap(normalBitmapData);
            return normalMap;
        }
        
        
    }
//}

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
 * You may add additional accurate notices of copyright ownership.
 *
 * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ 
 * */

//package engine3d.materials {

    import alternativa.engine3d.alternativa3d;
    import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.core.DrawUnit;
    import alternativa.engine3d.core.Light3D;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.core.Renderer;
    import alternativa.engine3d.core.Transform3D;
    import alternativa.engine3d.core.VertexAttributes;
    import alternativa.engine3d.lights.DirectionalLight;
    import alternativa.engine3d.lights.OmniLight;
    import alternativa.engine3d.lights.SpotLight;
    import alternativa.engine3d.materials.A3DUtils;
    import alternativa.engine3d.materials.compiler.Linker;
    import alternativa.engine3d.materials.compiler.Procedure;
    import alternativa.engine3d.materials.compiler.VariableType;
    import alternativa.engine3d.materials.Material;
    import alternativa.engine3d.materials.NormalMapSpace;
    import alternativa.engine3d.materials.TextureMaterial;
    import alternativa.engine3d.objects.Surface;
    import alternativa.engine3d.resources.Geometry;
    import alternativa.engine3d.resources.TextureResource;
    import flash.geom.Vector3D;

    import avmplus.getQualifiedClassName;

    import flash.display3D.Context3D;
    import flash.display3D.Context3DBlendFactor;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Context3DVertexBufferFormat;
    import flash.display3D.VertexBuffer3D;
    import flash.utils.Dictionary;
    import flash.utils.getDefinitionByName;

    use namespace alternativa3d;

    /**
     * Material with diffuse, normal, opacity, specular maps and glossiness value. The material is able to draw skin
     * with the number of bones in surface no more than 41. To reduce the number of bones in surface can break
     * the skin for more surface with fewer bones. Use the method Skin.divide (). To be drawn with this material,
     * geometry should have UV coordinates vertex normals and tangent and binormal values​​.
     *
     * @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS
     * @see alternativa.engine3d.core.VertexAttributes#NORMAL
     * @see alternativa.engine3d.core.VertexAttributes#TANGENT4
     * @see alternativa.engine3d.objects.Skin#divide()
     */
    //public 
    class PlaneCutMaterial extends TextureMaterial {

        private static const LIGHT_MAP_BIT:int = 1;
        private static const GLOSSINESS_MAP_BIT:int = 2;
        private static const SPECULAR_MAP_BIT:int = 4;
        private static const OPACITY_MAP_BIT:int = 8;
        private static const NORMAL_MAP_SPACE_OFFSET:int = 4;    // shift value
        private static const ALPHA_TEST_OFFSET:int = 6;
        private static const OMNI_LIGHT_OFFSET:int = 8;
        private static const DIRECTIONAL_LIGHT_OFFSET:int = 11;
        private static const SPOT_LIGHT_OFFSET:int = 14;
        private static const SHADOW_OFFSET:int = 17;
        // TODO: remove double cash by transform procedure. It increase speed by 1%
//        private static const OBJECT_TYPE_BIT:int = 19;

        private static var caches:Dictionary = new Dictionary(true);
        private var cachedContext3D:Context3D;
        private var programsCache:Dictionary;
        private var groups:Vector.<Vector.<Light3D>> = new Vector.<Vector.<Light3D>>();
        
        
        // Dependencies to set!!!
        [Inject] public var planeResult:PlaneResult;
        
        
        /**
         * @private
         */
        alternativa3d static const DISABLED:int = 0;
        /**
         * @private
         */
        alternativa3d static const SIMPLE:int = 1;
        /**
         * @private
         */
        alternativa3d static const ADVANCED:int = 2;

        /**
         * @private
         */
        alternativa3d static var fogMode:int = DISABLED;
        /**
         * @private
         */
        alternativa3d static var fogNear:Number = 1000;
        /**
         * @private
         */
        alternativa3d static var fogFar:Number = 5000;

        /**
         * @private
         */
        alternativa3d static var fogMaxDensity:Number = 1;

        /**
         * @private
         */
        alternativa3d static var fogColorR:Number = 0xC8/255;
        /**
         * @private
         */
        alternativa3d static var fogColorG:Number = 0xA2/255;
        /**
         * @private
         */
        alternativa3d static var fogColorB:Number = 0xC8/255;

        /**
         * @private
         */
        alternativa3d static var fogTexture:TextureResource;
        
        ///*
        static alternativa3d const getDiffuseProcedure:Procedure = new Procedure([
            "#v0=vUV",
            "#v1=vPosition",  // var position
            "#s0=sDiffuse",
            "#c0=cThresholdAlpha",
            "#c1=cPlane", // start plane test
            "dp3 t0.x, v1, c1",
            "sub t0.x, t0.x, c1.w",
            "kil t0.x",  // end plane test
            "tex t0, v0, s0 <2d, linear,repeat, miplinear>",
            "mul t0.w, t0.w, c0.w",
            "mov o0, t0"
        ], "getDiffuseProcedure");
        //*/
        
        //maxX*plane.x + maxY*plane.y + maxZ*plane.z <= plane.offset
        
            /**
         * @private
         * Procedure for diffuse with opacity map.
         */
        static alternativa3d const getDiffuseOpacityProcedure:Procedure = new Procedure([
            "#v0=vUV",
            "#v1=vPosition",  // var position
            "#s0=sDiffuse",
            "#s1=sOpacity",
            "#c0=cThresholdAlpha",
            "#c1=cPlane",  // start plane test
            "dp3 t0.x, v1, c1",
            "sub t0.x, t0.x, c1.w",
            "kil t0.x",  // end plane test
            "tex t0, v0, s0 <2d, linear,repeat, miplinear>",
            "tex t1, v0, s1 <2d, linear,repeat, miplinear>",
            "mul t0.w, t1.x, c0.w",
            "mov o0, t0"
        ], "getDiffuseOpacityProcedure");

        // inputs : position
        private static const _passVaryingsProcedure:Procedure = new Procedure([
            "#v0=vPosition",
            "#v1=vViewVector",
            "#c0=cCameraPosition",
            // Pass the position
            "mov v0, i0",
            // Vector  to Camera
            "sub t0, c0, i0",
            "mov v1.xyz, t0.xyz",
            "mov v1.w, c0.w"
        ]);
        
        /*
         private static const passPositionProcedure:Procedure = new Procedure([
                "#v1=vPosition",
                "mov v1, i0"
            ], "passPositionProcedure" );
        */
            
        // inputs : tangent, normal
        private static const _passTBNRightProcedure:Procedure = getPassTBNProcedure(true);
        private static const _passTBNLeftProcedure:Procedure = getPassTBNProcedure(false);
        private static function getPassTBNProcedure(right:Boolean):Procedure {
            var crsInSpace:String = (right) ? "crs t1.xyz, i0, i1" : "crs t1.xyz, i1, i0";
            return new Procedure([
                "#v0=vTangent",
                "#v1=vBinormal",
                "#v2=vNormal",
                // Calculate binormal
                crsInSpace,
                "mul t1.xyz, t1.xyz, i0.w",
                // Транспонируем матрицу нормалей
                "mov v0.xyzw, i1.xyxw",
                "mov v0.x, i0.x",
                "mov v0.y, t1.x",
                "mov v1.xyzw, i1.xyyw",
                "mov v1.x, i0.y",
                "mov v1.y, t1.y",
                "mov v2.xyzw, i1.xyzw",
                "mov v2.x, i0.z",
                "mov v2.y, t1.z"
            ], "passTBNProcedure");
        }

        // outputs : light, highlight
        private static const _ambientLightProcedure:Procedure = new Procedure([
            "#c0=cSurface",
            "mov o0, i0",
            "mov o1, c0.xxxx"
        ], "ambientLightProcedure");

        // Set o.w to glossiness
        private static const _setGlossinessFromConstantProcedure:Procedure = new Procedure([
            "#c0=cSurface",
            "mov o0.w, c0.y"
        ], "setGlossinessFromConstantProcedure");
        // Set o.w to glossiness from texture
        private static const _setGlossinessFromTextureProcedure:Procedure = new Procedure([
            "#v0=vUV",
            "#c0=cSurface",
            "#s0=sGlossiness",
            "tex t0, v0, s0 <2d, repeat, linear, miplinear>",
            "mul o0.w, t0.x, c0.y"
        ], "setGlossinessFromTextureProcedure");

        // outputs : normal, viewVector
        private static const _getNormalAndViewTangentProcedure:Procedure = new Procedure([
            "#v0=vTangent",
            "#v1=vBinormal",
            "#v2=vNormal",
            "#v3=vUV",
            "#v4=vViewVector",
            "#c0=cAmbientColor",
            "#s0=sBump",
            // Extract normal from the texture
            "tex t0, v3, s0 <2d,repeat,linear,miplinear>",
            "add t0, t0, t0",
            "sub t0.xyz, t0.xyz, c0.www",
            // Transform the normal with TBN
            "nrm t1.xyz, v0.xyz",
            "dp3 o0.x, t0.xyz, t1.xyz",
            "nrm t1.xyz, v1.xyz",
            "dp3 o0.y, t0.xyz, t1.xyz",
            "nrm t1.xyz, v2.xyz",
            "dp3 o0.z, t0.xyz, t1.xyz",
            // Normalization
            "nrm o0.xyz, o0.xyz",
            // Returns normalized vector of view
            "nrm o1.xyz, v4"
        ], "getNormalAndViewTangentProcedure");
        // outputs : normal, viewVector
        private static const _getNormalAndViewObjectProcedure:Procedure = new Procedure([
            "#v3=vUV",
            "#v4=vViewVector",
            "#c0=cAmbientColor",
            "#s0=sBump",
            // Extract normal from the texture
            "tex t0, v3, s0 <2d,repeat,linear,miplinear>",
            "add t0, t0, t0",
            "sub t0.xyz, t0.xyz, c0.www",
            // Normalization
            "nrm o0.xyz, t0.xyz",
            // Returns normalized vector of view
            "nrm o1.xyz, v4"
        ], "getNormalAndViewObjectProcedure");

        // Apply specular map color to a flare
        private static const _applySpecularProcedure:Procedure = new Procedure([
            "#v0=vUV",
            "#s0=sSpecular",
            "tex t0, v0, s0 <2d, repeat,linear,miplinear>",
            "mul o0.xyz, o0.xyz, t0.xyz"
        ], "applySpecularProcedure");

        //Apply light and flare to diffuse
        // inputs : "diffuse", "tTotalLight", "tTotalHighLight"
        private static const _mulLightingProcedure:Procedure = new Procedure([
            "#c0=cSurface",  // c0.z - specularPower
            "mul i0.xyz, i0.xyz, i1.xyz",
            "mul t1.xyz, i2.xyz, c0.z",
            "add i0.xyz, i0.xyz, t1.xyz",
            "mov o0, i0"
        ], "mulLightingProcedure");

        // inputs : position
        private static const passSimpleFogConstProcedure:Procedure = new Procedure([
            "#v0=vZDistance",
            "#c0=cFogSpace",
            "dp4 t0.z, i0, c0",
            "mov v0, t0.zzzz",
            "sub v0.y, i0.w, t0.z"
        ], "passSimpleFogConst");

        // inputs : color
        private static const outputWithSimpleFogProcedure:Procedure = new Procedure([
            "#v0=vZDistance",
            "#c0=cFogColor",
            "#c1=cFogRange",
            // Restrict fog factor with the range
            "min t0.xy, v0.xy, c1.xy",
            "max t0.xy, t0.xy, c1.zw",
            "mul i0.xyz, i0.xyz, t0.y",
            "mul t0.xyz, c0.xyz, t0.x",
            "add i0.xyz, i0.xyz, t0.xyz",
            "mov o0, i0"
        ], "outputWithSimpleFog");

        // inputs : position, projected
        private static const postPassAdvancedFogConstProcedure:Procedure = new Procedure([
            "#v0=vZDistance",
            "#c0=cFogSpace",
            "dp4 t0.z, i0, c0",
            "mov v0, t0.zzzz",
            "sub v0.y, i0.w, t0.z",
            // Screen x coordinate
            "mov v0.zw, i1.xwxw",
            "mov o0, i1"
        ], "postPassAdvancedFogConst");

        // inputs : color
        private static const outputWithAdvancedFogProcedure:Procedure = new Procedure([
            "#v0=vZDistance",
            "#c0=cFogConsts",
            "#c1=cFogRange",
            "#s0=sFogTexture",
            // Restrict fog factor with the range
            "min t0.xy, v0.xy, c1.xy",
            "max t0.xy, t0.xy, c1.zw",
            "mul i0.xyz, i0.xyz, t0.y",
            // Calculate fog color
            "mov t1.xyzw, c0.yyzw",
            "div t0.z, v0.z, v0.w",
            "mul t0.z, t0.z, c0.x",
            "add t1.x, t1.x, t0.z",
            "tex t1, t1, s0 <2d, repeat, linear, miplinear>",
            "mul t0.xyz, t1.xyz, t0.x",
            "add i0.xyz, i0.xyz, t0.xyz",
            "mov o0, i0"
        ], "outputWithAdvancedFog");

        // Add lightmap value with light
        private static const _addLightMapProcedure:Procedure = new Procedure([
            "#v0=vUV1",
            "#s0=sLightMap",
            "tex t0, v0, s0 <2d,repeat,linear,miplinear>",
            "add t0, t0, t0",
            "add o0.xyz, i0.xyz, t0.xyz"
        ], "applyLightMapProcedure");

        private static const _passLightMapUVProcedure:Procedure = new Procedure([
            "#a0=aUV1",
            "#v0=vUV1",
            "mov v0, a0"
        ], "passLightMapUVProcedure");

        /**
         * Normal map.
         */
        public var normalMap:TextureResource;

        private var _normalMapSpace:int = NormalMapSpace.TANGENT_RIGHT_HANDED;
        /**
         * Type of the normal map. Should be defined by constants of   <code>NormalMapSpace</code> class.
         *
         * @default NormalMapSpace.TANGENT
         *
         * @see NormalMapSpace
         */
        public function get normalMapSpace():int {
            return _normalMapSpace;
        }

        /**
         * @private
         */
        public function set normalMapSpace(value:int):void {
            if (value != NormalMapSpace.TANGENT_RIGHT_HANDED && value != NormalMapSpace.TANGENT_LEFT_HANDED && value != NormalMapSpace.OBJECT) {
                throw new ArgumentError("Value must be a constant from the NormalMapSpace class");
            }
            _normalMapSpace = value;
        }

        /**
         * Specular map.
         */
        public var specularMap:TextureResource;
        /**
         * Glossiness map.
         */
        public var glossinessMap:TextureResource;

        /**
         * Light map.
         */
        public var lightMap:TextureResource;

        /**
         * Number of the UV-channel for light map.
         */
        public var lightMapChannel:uint = 0;
        /**
         * Glossiness. Multiplies with  <code>glossinessMap</code> value.
         */
        public var glossiness:Number = 100;

        /**
         * Brightness of a flare. Multiplies with  <code>specularMap</code> value.
         */
        public var specularPower:Number = 1;

        /**
         * Creates a new PlaneCutMaterial instance.
         * @param diffuseMap Diffuse map.
         * @param normalMap Normal map.
         * @param specularMap Specular map.
         * @param glossinessMap Glossiness map.
         * @param opacityMap Opacity map.
         */
        public function PlaneCutMaterial(diffuseMap:TextureResource = null, normalMap:TextureResource = null, specularMap:TextureResource = null, glossinessMap:TextureResource = null, opacityMap:TextureResource = null) {
            super(diffuseMap, opacityMap);
            this.normalMap = normalMap;
            this.specularMap = specularMap;
            this.glossinessMap = glossinessMap;
        }

        /**
         * @private
         */
        override alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void {
            super.fillResources(resources, resourceType);
            if (normalMap != null &&
                    A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(normalMap)) as Class, resourceType)) {
                resources[normalMap] = true;
            }

            if (lightMap != null &&
                    A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(lightMap)) as Class, resourceType)) {
                resources[lightMap] = true;
            }
            if (glossinessMap != null &&
                    A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(glossinessMap)) as Class, resourceType)) {
                resources[glossinessMap] = true;
            }
            if (specularMap != null &&
                    A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(specularMap)) as Class, resourceType)) {
                resources[specularMap] = true;
            }
        }

        /**
         * @private
         */
        alternativa3d function getPassUVProcedure():Procedure {
            return _passUVProcedure;
        }

        /**
         * @private
         */
        alternativa3d function setPassUVProcedureConstants(destination:DrawUnit, vertexLinker:Linker):void {
        }

        // inputs: tNormal", "tViewVector", "shadow", "cAmbientColor"
        // outputs : light, hightlight
        private function formDirectionalProcedure(procedure:Procedure, index:int, useShadow:Boolean):void {
            var source:Array = [
                // Position - dirction vector of light
                "#c0=c" + index + "Position",
                "#c1=c" + index + "Color",
                // Calculate half-way vector
                "add t0.xyz, i1.xyz, c0.xyz",
                "mov t0.w, c0.w",
                "nrm t0.xyz,t0.xyz",
                // Calculate a flare
                "dp3 t0.w, t0.xyz, i0.xyz",
                "pow t0.w, t0.w, o1.w",
                // Calculate light
                "dp3 t0.x, i0.xyz, c0.xyz",
                "sat t0.x, t0.x"
            ];
            if (useShadow) {
                source.push("mul t0.xw, t0.xw, i2.x");
                source.push("mul t0.xyz, c1.xyz, t0.xxx");
                source.push("add o0.xyz, t0.xyz, i3.xyz");
                source.push("mul o1.xyz, c1.xyz, t0.www");
            } else {
                // Apply calculated values
                source.push("mul t0.xyz, c1.xyz, t0.xxxx");
                source.push("add o0, o0, t0.xyz");
                source.push("mul t0.xyz, c1.xyz, t0.w");
                source.push("add o1.xyz, o1.xyz, t0.xyz");
            }
            procedure.compileFromArray(source);
        }

        private function formOmniProcedure(procedure:Procedure, index:int, useShadow:Boolean):void {
//            fragmentLinker.setInputParams(omniMulShadowProcedure, "tNormal", "tViewVector", "tTotalLight", "cAmbientColor");
            var source:Array = [
                "#c0=c" + index + "Position",
                "#c1=c" + index + "Color",
                "#c2=c" + index + "Radius",
                "#v0=vPosition"
            ];
            if (useShadow) {
                // Считаем вектор из точки к свету
                source.push("sub t0, c0, v0"); // L = lightPos - PointPos
                source.push("dp3 t0.w, t0.xyz, t0.xyz"); // lenSqr
                source.push("nrm t0.xyz, t0.xyz"); // L = normalize(L)
                // Считаем half-way вектор
                source.push("add t1.xyz, i1.xyz, t0.xyz");
                source.push("nrm t1.xyz, t1.xyz");
                // Считаем блик
                source.push("dp3 t1.w, t1.xyz, i0.xyz");
                source.push("pow t1.w, t1.w, o1.w");
                // Считаем расстояние до источника света
                source.push("sqt t1.x, t0.w"); // len = sqt(lensqr)
                // Считаем свет
                source.push("dp3 t0.w, t0.xyz, i0.xyz"); // dot = dot(normal, L)
                // Считаем затухание
                source.push("sub t0.x, t1.x, c2.z"); // len = len - atenuationBegin
                source.push("div t0.y, t0.x, c2.y"); // att = len/radius
                source.push("sub t0.x, c2.x, t0.y"); // att = 1 - len/radius
                source.push("sat t0.xw, t0.xw"); // t = max(t, 0)

                // i3 - ambient
                // i2 - shadow-test

                source.push("mul t0.xw,   t0.xwww,   i2.xxxx");
                source.push("mul t0.xyz, c1.xyz, t0.xxx");     // t = color*t
                source.push("mul t1.xyz, t0.xyz, t1.w");
                source.push("add o1.xyz, o1.xyz, t1.xyz");
                source.push("mul t0.xyz, t0.xyz, t0.www");
                source.push("add o0.xyz, t0.xyz, i3.xyz");
            } else {
                // Считаем вектор из точки к свету
                source.push("sub t0, c0, v0"); // L = lightPos - PointPos
                source.push("dp3 t0.w, t0.xyz, t0.xyz"); // lenSqr
                source.push("nrm t0.xyz, t0.xyz"); // L = normalize(L)
                // Считаем half-way вектор
                source.push("add t1.xyz, i1.xyz, t0.xyz");
                source.push("mov t1.w, c0.w");
                source.push("nrm t1.xyz, t1.xyz");
                // Считаем блик
                source.push("dp3 t1.w, t1.xyz, i0.xyz");
                source.push("pow t1.w, t1.w, o1.w");            //!!!
                // Считаем расстояние до источника света
                source.push("sqt t1.x, t0.w"); // len = sqt(lensqr)
                // Считаем свет
                source.push("dp3 t0.w, t0.xyz, i0.xyz"); // dot = dot(normal, L)
                // Считаем затухание
                source.push("sub t0.x, t1.x, c2.z"); // len = len - atenuationBegin
                source.push("div t0.y, t0.x, c2.y"); // att = len/radius
                source.push("sub t0.x, c2.x, t0.y"); // att = 1 - len/radius
                source.push("sat t0.xw, t0.xw"); // t = max(t, 0)

                // Перемножаем цвет источника с затуханием
                source.push("mul t0.xyz, c1.xyz, t0.xxx");     // t = color*t
                source.push("mul t1.xyz, t0.xyz, t1.w");
                source.push("add o1.xyz, o1.xyz, t1.xyz");
                source.push("mul t0.xyz, t0.xyz, t0.www");
                source.push("add o0.xyz, o0.xyz, t0.xyz");
            }

            procedure.compileFromArray(source);
        }

        /**
         * @param object
         * @param materialKey
         * @param opacityMap
         * @param alphaTest 0:disabled 1:alpha-test 2:contours
         * @param lightsGroup
         * @param directionalLight
         * @param lightsLength
         */
        private function getProgram(object:Object3D, programs:Array, camera:Camera3D, materialKey:int, opacityMap:TextureResource, alphaTest:int, lightsGroup:Vector.<Light3D>, lightsLength:int, isFirstGroup:Boolean, shadowedLight:Light3D):PlaneCutMaterialProgram {
            // 0 bit - lightmap
            // 1 bit - glossiness map
            // 2 bit - opacity map
            // 3 bit - specular map
            // 4-5 bits - normalMapSpace
            // 6-7 bits - alphaTest
            // 8-10 bits - OmniLight count
            // 11-13 bits - DirectionalLight count
            // 14-16 bits - SpotLight count
            // 17-18 bit - Shadow Type (PCF, SIMPLE, NONE)

            var key:int = materialKey | (opacityMap != null ? OPACITY_MAP_BIT : 0) | (alphaTest << ALPHA_TEST_OFFSET);
            var program:PlaneCutMaterialProgram = programs[key];

            if (program == null) {
                var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX);
                var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT);
                var i:int;

                // Merge program using lightsGroup
                // add property useShadow

                fragmentLinker.declareVariable("tTotalLight");
                fragmentLinker.declareVariable("tTotalHighLight");
                fragmentLinker.declareVariable("tNormal");

                if (isFirstGroup){
                    fragmentLinker.declareVariable("cAmbientColor", VariableType.CONSTANT);
                    fragmentLinker.addProcedure(_ambientLightProcedure);
                    fragmentLinker.setInputParams(_ambientLightProcedure, "cAmbientColor");
                    fragmentLinker.setOutputParams(_ambientLightProcedure, "tTotalLight", "tTotalHighLight");

                    if (lightMap != null) {
                        vertexLinker.addProcedure(_passLightMapUVProcedure);
                        fragmentLinker.addProcedure(_addLightMapProcedure);
                        fragmentLinker.setInputParams(_addLightMapProcedure, "tTotalLight");
                        fragmentLinker.setOutputParams(_addLightMapProcedure, "tTotalLight");
                    }
                }
                else{
                    // сбросить tTotalLight tTotalHighLight
                    fragmentLinker.declareVariable("cAmbientColor", VariableType.CONSTANT);
                    fragmentLinker.addProcedure(_ambientLightProcedure);
                    fragmentLinker.setInputParams(_ambientLightProcedure, "cAmbientColor");
                    fragmentLinker.setOutputParams(_ambientLightProcedure, "tTotalLight", "tTotalHighLight");
                }

                var positionVar:String = "aPosition";
                var normalVar:String = "aNormal";
                var tangentVar:String = "aTangent";
                vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE);
                vertexLinker.declareVariable(tangentVar, VariableType.ATTRIBUTE);
                vertexLinker.declareVariable(normalVar, VariableType.ATTRIBUTE);
                if (object.transformProcedure != null) {
                    positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker);
                }

                vertexLinker.addProcedure(_projectProcedure);
                vertexLinker.setInputParams(_projectProcedure, positionVar);

                vertexLinker.addProcedure(getPassUVProcedure());
                // vertexLinker.addProcedure(passPositionProcedure);
                   // vertexLinker.setInputParams(passPositionProcedure, positionVar);

                if (glossinessMap != null) {
                    fragmentLinker.addProcedure(_setGlossinessFromTextureProcedure);
                    fragmentLinker.setOutputParams(_setGlossinessFromTextureProcedure, "tTotalHighLight");
                } else {
                    fragmentLinker.addProcedure(_setGlossinessFromConstantProcedure);
                    fragmentLinker.setOutputParams(_setGlossinessFromConstantProcedure, "tTotalHighLight");
                }

                if (lightsLength > 0 || shadowedLight) {
                    var procedure:Procedure;
                    if (object.deltaTransformProcedure != null) {
                        vertexLinker.declareVariable("tTransformedNormal");
                        procedure = object.deltaTransformProcedure.newInstance();
                        vertexLinker.addProcedure(procedure);
                        vertexLinker.setInputParams(procedure, normalVar);
                        vertexLinker.setOutputParams(procedure, "tTransformedNormal");
                        normalVar = "tTransformedNormal";

                        vertexLinker.declareVariable("tTransformedTangent");
                        procedure = object.deltaTransformProcedure.newInstance();
                        vertexLinker.addProcedure(procedure);
                        vertexLinker.setInputParams(procedure, tangentVar);
                        vertexLinker.setOutputParams(procedure, "tTransformedTangent");
                        tangentVar = "tTransformedTangent";
                    }
                    vertexLinker.addProcedure(_passVaryingsProcedure);
                    
                    vertexLinker.setInputParams(_passVaryingsProcedure, positionVar);
                    fragmentLinker.declareVariable("tViewVector");

                    if (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) {
                        var nrmProcedure:Procedure = (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED) ? _passTBNRightProcedure : _passTBNLeftProcedure;
                        vertexLinker.addProcedure(nrmProcedure);
                        vertexLinker.setInputParams(nrmProcedure, tangentVar, normalVar);
                        fragmentLinker.addProcedure(_getNormalAndViewTangentProcedure);
                        fragmentLinker.setOutputParams(_getNormalAndViewTangentProcedure, "tNormal", "tViewVector");
                    } else {
                        fragmentLinker.addProcedure(_getNormalAndViewObjectProcedure);
                        fragmentLinker.setOutputParams(_getNormalAndViewObjectProcedure, "tNormal", "tViewVector");
                    }
                    if (shadowedLight != null) {
                        var shadowProc:Procedure;
                        if (shadowedLight is DirectionalLight){
                            vertexLinker.addProcedure(shadowedLight.shadow.vertexShadowProcedure, positionVar);
                            shadowProc = shadowedLight.shadow.fragmentShadowProcedure;
                            fragmentLinker.addProcedure(shadowProc);
                            fragmentLinker.setOutputParams(shadowProc, "tTotalLight");

                            var dirMulShadowProcedure:Procedure = new Procedure(null, "lightShadowDirectional");
                            formDirectionalProcedure(dirMulShadowProcedure, 0, true);
                            fragmentLinker.addProcedure(dirMulShadowProcedure);
                            fragmentLinker.setInputParams(dirMulShadowProcedure, "tNormal", "tViewVector", "tTotalLight", "cAmbientColor");
                            fragmentLinker.setOutputParams(dirMulShadowProcedure, "tTotalLight", "tTotalHighLight");
                        }

                        if (shadowedLight is OmniLight){
                            vertexLinker.addProcedure(shadowedLight.shadow.vertexShadowProcedure, positionVar);
                            shadowProc = shadowedLight.shadow.fragmentShadowProcedure;
                            fragmentLinker.addProcedure(shadowProc);
                            fragmentLinker.setOutputParams(shadowProc, "tTotalLight");

                            var omniMulShadowProcedure:Procedure = new Procedure(null, "lightShadowDirectional");
                            formOmniProcedure(omniMulShadowProcedure, 0, true);
                            fragmentLinker.addProcedure(omniMulShadowProcedure);
                            fragmentLinker.setInputParams(omniMulShadowProcedure, "tNormal", "tViewVector", "tTotalLight", "cAmbientColor");
                            fragmentLinker.setOutputParams(omniMulShadowProcedure, "tTotalLight", "tTotalHighLight");
                        }
                    }

                    for (i = 0; i < lightsLength; i++) {
                        var light:Light3D = lightsGroup[i];
                        if (light == shadowedLight && (shadowedLight is DirectionalLight || shadowedLight is OmniLight)) continue;
                        var lightFragmentProcedure:Procedure = new Procedure();
                        lightFragmentProcedure.name = "light" + i.toString();
                        if (light is DirectionalLight) {
                            formDirectionalProcedure(lightFragmentProcedure, i, false);
                            lightFragmentProcedure.name += "Directional";
                        } else if (light is OmniLight) {
                            formOmniProcedure(lightFragmentProcedure, i, false);
                            lightFragmentProcedure.name += "Omni";
                        } else if (light is SpotLight) {
                            lightFragmentProcedure.compileFromArray([
                                "#c0=c" + i + "Position",
                                "#c1=c" + i + "Color",
                                "#c2=c" + i + "Radius",
                                "#c3=c" + i + "Axis",
                                "#v0=vPosition",
                                // Calculate vector from the point to light
                                "sub t0, c0, v0",// L = pos - lightPos
                                "dp3 t0.w, t0, t0",// lenSqr
                                "nrm t0.xyz,t0.xyz",// L = normalize(L)
                                // Calculate half-way vector
                                "add t2.xyz, i1.xyz, t0.xyz",
                                "nrm t2.xyz, t2.xyz",
                                //Calculate a flare
                                "dp3 t2.x, t2.xyz, i0.xyz",
                                "pow t2.x, t2.x, o1.w",
                                "dp3 t1.x, t0.xyz, c3.xyz", //axisDirDot
                                "dp3 t0.x, t0, i0.xyz",// dot = dot(normal, L)
                                "sqt t0.w, t0.w",// len = sqt(lensqr)
                                "sub t0.w, t0.w, c2.y",// len = len - atenuationBegin
                                "div t0.y, t0.w, c2.x",// att = len/radius
                                "sub t0.w, c0.w, t0.y",// att = 1 - len/radius
                                "sub t0.y, t1.x, c2.w",
                                "div t0.y, t0.y, c2.z",
                                "sat t0.xyw,t0.xyw",// t = sat(t)
                                "mul t1.xyz,c1.xyz,t0.yyy",// t = color*t
                                "mul t1.xyz,t1.xyz,t0.www",//
                                "mul t2.xyz, t2.x, t1.xyz",
                                "add o1.xyz, o1.xyz, t2.xyz",
                                "mul t1.xyz, t1.xyz, t0.xxx",

                                "add o0.xyz, o0.xyz, t1.xyz"
                            ]);
                            lightFragmentProcedure.name += "Spot";
                        }
                        fragmentLinker.addProcedure(lightFragmentProcedure);
                        fragmentLinker.setInputParams(lightFragmentProcedure, "tNormal", "tViewVector");
                        fragmentLinker.setOutputParams(lightFragmentProcedure, "tTotalLight", "tTotalHighLight");
                    }
                }

                var outputProcedure:Procedure;
                if (specularMap != null) {
                    fragmentLinker.addProcedure(_applySpecularProcedure);
                    fragmentLinker.setOutputParams(_applySpecularProcedure, "tTotalHighLight");
                    outputProcedure = _applySpecularProcedure;
                }

                fragmentLinker.declareVariable("tColor");
                outputProcedure = opacityMap != null ? getDiffuseOpacityProcedure : getDiffuseProcedure;
                
                fragmentLinker.addProcedure(outputProcedure);
                fragmentLinker.setOutputParams(outputProcedure, "tColor");

                if (alphaTest > 0) {
                    outputProcedure = alphaTest == 1 ? thresholdOpaqueAlphaProcedure : thresholdTransparentAlphaProcedure;
                    fragmentLinker.addProcedure(outputProcedure, "tColor");
                    fragmentLinker.setOutputParams(outputProcedure, "tColor");
                }

                fragmentLinker.addProcedure(_mulLightingProcedure, "tColor", "tTotalLight", "tTotalHighLight");


//                if (fogMode == SIMPLE || fogMode == ADVANCED) {
//                    fragmentLinker.setOutputParams(_mulLightingProcedure, "tColor");
//                }
//                if (fogMode == SIMPLE) {
//                    vertexLinker.addProcedure(passSimpleFogConstProcedure);
//                    vertexLinker.setInputParams(passSimpleFogConstProcedure, positionVar);
//                    fragmentLinker.addProcedure(outputWithSimpleFogProcedure);
//                    fragmentLinker.setInputParams(outputWithSimpleFogProcedure, "tColor");
//                    outputProcedure = outputWithSimpleFogProcedure;
//                } else if (fogMode == ADVANCED) {
//                    vertexLinker.declareVariable("tProjected");
//                    vertexLinker.setOutputParams(_projectProcedure, "tProjected");
//                    vertexLinker.addProcedure(postPassAdvancedFogConstProcedure);
//                    vertexLinker.setInputParams(postPassAdvancedFogConstProcedure, positionVar, "tProjected");
//                    fragmentLinker.addProcedure(outputWithAdvancedFogProcedure);
//                    fragmentLinker.setInputParams(outputWithAdvancedFogProcedure, "tColor");
//                    outputProcedure = outputWithAdvancedFogProcedure;
//                }

                fragmentLinker.varyings = vertexLinker.varyings;
                program = new PlaneCutMaterialProgram(vertexLinker, fragmentLinker, (shadowedLight != null) ? 1 : lightsLength);

                program.upload(camera.context3D);
                programs[key] = program;
            }
            return program;
        }

        private function addDrawUnits(program:PlaneCutMaterialProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource, lights:Vector.<Light3D>, lightsLength:int, isFirstGroup:Boolean, shadowedLight:Light3D, opaqueOption:Boolean, transparentOption:Boolean, objectRenderPriority:int):void {
            // Buffers
            var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
            var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
            var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
            var tangentsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TANGENT4);

            if (positionBuffer == null || uvBuffer == null) return;
            if ((lightsLength > 0 || shadowedLight != null) && (normalsBuffer == null || tangentsBuffer == null)) return;

            var object:Object3D = surface.object;

            // Draw call
            var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program);

            // Streams
            drawUnit.setVertexBufferAt(program.aPosition, positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]);
            drawUnit.setVertexBufferAt(program.aUV, uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]);

            // Constants
            object.setTransformConstants(drawUnit, surface, program.vertexShader, camera);
            drawUnit.setProjectionConstants(camera, program.cProjMatrix, object.localToCameraTransform);
             // Set options for a surface. X should be 0.
            drawUnit.setFragmentConstantsFromNumbers(program.cSurface, 0, glossiness, specularPower, 1);
            drawUnit.setFragmentConstantsFromNumbers(program.cThresholdAlpha, alphaThreshold, 0, 0, alpha);    
            
            var camTransform:Transform3D;//= object.cameraToLocalTransform;
            // calculate plane in local coordinate space
            
            
        if (planeResult == null) throw new Error("No plane result found!");
        
            drawUnit.setFragmentConstantsFromNumbers(program.cPlane, planeResult.v.x, planeResult.v.y, planeResult.v.z, planeResult.v.w);
            
            

            var light:Light3D;
            var len:Number;
            var transform:Transform3D;
            var rScale:Number;
            var omni:OmniLight;
            var spot:SpotLight;
            var falloff:Number;
            var hotspot:Number;

            if (lightsLength > 0 || shadowedLight != null) {
                if (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED) {
                    drawUnit.setVertexBufferAt(program.aNormal, normalsBuffer, geometry._attributesOffsets[VertexAttributes.NORMAL], VertexAttributes.FORMATS[VertexAttributes.NORMAL]);
                    drawUnit.setVertexBufferAt(program.aTangent, tangentsBuffer, geometry._attributesOffsets[VertexAttributes.TANGENT4], VertexAttributes.FORMATS[VertexAttributes.TANGENT4]);
                }
                drawUnit.setTextureAt(program.sBump, normalMap._texture);

                camTransform = object.cameraToLocalTransform;
                drawUnit.setVertexConstantsFromNumbers(program.cCameraPosition, camTransform.d, camTransform.h, camTransform.l);

                for (var i:int = 0; i < lightsLength; i++) {
                    light = lights[i];
                    if (light is DirectionalLight) {
                        transform = light.lightToObjectTransform;
                        len = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k);

                        drawUnit.setFragmentConstantsFromNumbers(program.cPosition[i], -transform.c/len, -transform.g/len, -transform.k/len, 1);
                        drawUnit.setFragmentConstantsFromNumbers(program.cColor[i], light.red, light.green, light.blue);
                    } else if (light is OmniLight) {
                        omni = light as OmniLight;
                        transform = light.lightToObjectTransform;
                        rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i);
                        rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j);
                        rScale += Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k);
                        rScale /= 3;

                        drawUnit.setFragmentConstantsFromNumbers(program.cPosition[i], transform.d, transform.h, transform.l);
                        drawUnit.setFragmentConstantsFromNumbers(program.cRadius[i], 1, omni.attenuationEnd*rScale - omni.attenuationBegin*rScale, omni.attenuationBegin*rScale);
                        drawUnit.setFragmentConstantsFromNumbers(program.cColor[i], light.red, light.green, light.blue);
                    } else if (light is SpotLight) {
                        spot = light as SpotLight;
                        transform = light.lightToObjectTransform;
                        rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i);
                        rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j);
                        rScale += len = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k);
                        rScale /= 3;
                        falloff = Math.cos(spot.falloff*0.5);
                        hotspot = Math.cos(spot.hotspot*0.5);

                        drawUnit.setFragmentConstantsFromNumbers(program.cPosition[i], transform.d, transform.h, transform.l);
                        drawUnit.setFragmentConstantsFromNumbers(program.cAxis[i], -transform.c/len, -transform.g/len, -transform.k/len);
                        drawUnit.setFragmentConstantsFromNumbers(program.cRadius[i], spot.attenuationEnd*rScale - spot.attenuationBegin*rScale, spot.attenuationBegin*rScale, hotspot == falloff ? 0.000001 : hotspot - falloff, falloff);
                        drawUnit.setFragmentConstantsFromNumbers(program.cColor[i], light.red, light.green, light.blue);
                    }
                }
            }

            if (shadowedLight != null) {
                light = shadowedLight;
                if (light is DirectionalLight) {
                    transform = light.lightToObjectTransform;
                    len = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k);
                    drawUnit.setFragmentConstantsFromNumbers(program.cPosition[0], -transform.c/len, -transform.g/len, -transform.k/len, 1);
                    drawUnit.setFragmentConstantsFromNumbers(program.cColor[0], light.red, light.green, light.blue);
                } else if (light is OmniLight) {
                    omni = light as OmniLight;
                    transform = light.lightToObjectTransform;
                    rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i);
                    rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j);
                    rScale += Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k);
                    rScale /= 3;
                    drawUnit.setFragmentConstantsFromNumbers(program.cPosition[0], transform.d, transform.h, transform.l);
                    drawUnit.setFragmentConstantsFromNumbers(program.cRadius[0], 1, omni.attenuationEnd*rScale - omni.attenuationBegin*rScale, omni.attenuationBegin*rScale);
                    drawUnit.setFragmentConstantsFromNumbers(program.cColor[0], light.red, light.green, light.blue);
                } else if (light is SpotLight) {
                    spot = light as SpotLight;
                    transform = light.lightToObjectTransform;
                    rScale = Math.sqrt(transform.a*transform.a + transform.e*transform.e + transform.i*transform.i);
                    rScale += Math.sqrt(transform.b*transform.b + transform.f*transform.f + transform.j*transform.j);
                    rScale += len = Math.sqrt(transform.c*transform.c + transform.g*transform.g + transform.k*transform.k);
                    rScale /= 3;
                    falloff = Math.cos(spot.falloff*0.5);
                    hotspot = Math.cos(spot.hotspot*0.5);

                    drawUnit.setFragmentConstantsFromNumbers(program.cPosition[0], transform.d, transform.h, transform.l);
                    drawUnit.setFragmentConstantsFromNumbers(program.cAxis[0], -transform.c/len, -transform.g/len, -transform.k/len);
                    drawUnit.setFragmentConstantsFromNumbers(program.cRadius[0], spot.attenuationEnd*rScale - spot.attenuationBegin*rScale, spot.attenuationBegin*rScale, hotspot == falloff ? 0.000001 : hotspot - falloff, falloff);
                    drawUnit.setFragmentConstantsFromNumbers(program.cColor[0], light.red, light.green, light.blue);
                }
            }

            // Textures
            drawUnit.setTextureAt(program.sDiffuse, diffuseMap._texture);
            if (opacityMap != null) {
                drawUnit.setTextureAt(program.sOpacity, opacityMap._texture);
            }
            if (glossinessMap != null) {
                drawUnit.setTextureAt(program.sGlossiness, glossinessMap._texture);
            }
            if (specularMap != null) {
                drawUnit.setTextureAt(program.sSpecular, specularMap._texture);
            }

            if (isFirstGroup) {
                if (lightMap != null) {
                    drawUnit.setVertexBufferAt(program.aUV1, geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[lightMapChannel]), geometry._attributesOffsets[VertexAttributes.TEXCOORDS[lightMapChannel]], Context3DVertexBufferFormat.FLOAT_2);
                    drawUnit.setFragmentConstantsFromNumbers(program.cAmbientColor, 0,0,0, 1);
                    drawUnit.setTextureAt(program.sLightMap, lightMap._texture);
                } else {
                    drawUnit.setFragmentConstantsFromVector(program.cAmbientColor, camera.ambient, 1);
                }
            }
            else{
                drawUnit.setFragmentConstantsFromNumbers(program.cAmbientColor, 0,0,0, 1);
            }
            setPassUVProcedureConstants(drawUnit, program.vertexShader);

            if (shadowedLight != null && ((shadowedLight is DirectionalLight)||(shadowedLight is OmniLight))) {
                shadowedLight.shadow.setup(drawUnit, program.vertexShader, program.fragmentShader, surface);
            }

            // Inititalizing render properties
            if (opaqueOption) {
                // Use z-buffer within DrawCall, draws without blending
                if (isFirstGroup){
                    drawUnit.blendSource = Context3DBlendFactor.ONE;
                    drawUnit.blendDestination = Context3DBlendFactor.ZERO;
                    camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE);
                }
                else{
                    drawUnit.blendSource = Context3DBlendFactor.ONE;
                    drawUnit.blendDestination = Context3DBlendFactor.ONE;
                    camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE_OVERHEAD);
                }
            }
            if (transparentOption){
                // Do not use z-buffer, draws with blending
                if (isFirstGroup){
                    drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA;
                    drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA;
                }
                else{
                    drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA;
                    drawUnit.blendDestination = Context3DBlendFactor.ONE;
                }
                camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT);
            }

//            if (fogMode == SIMPLE || fogMode == ADVANCED) {
//                var lm:Transform3D = object.localToCameraTransform;
//                var dist:Number = fogFar - fogNear;
//                drawUnit.setVertexConstantsFromNumbers(program.vertexShader.getVariableIndex("cFogSpace"), lm.i/dist, lm.j/dist, lm.k/dist, (lm.l - fogNear)/dist);
//                drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogRange"), fogMaxDensity, 1, 0, 1 - fogMaxDensity);
//            }
//            if (fogMode == SIMPLE) {
//                drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogColor"), fogColorR, fogColorG, fogColorB);
//            }
//            if (fogMode == ADVANCED) {
//                if (fogTexture == null) {
//                    var bmd:BitmapData = new BitmapData(32, 1, false, 0xFF0000);
//                    for (i = 0; i < 32; i++) {
//                        bmd.setPixel(i, 0, ((i/32)*255) << 16);
//                    }
//                    fogTexture = new BitmapTextureResource(bmd);
//                    fogTexture.upload(camera.context3D);
//                }
//                var cLocal:Transform3D = camera.localToGlobalTransform;
//                var halfW:Number = camera.view.width/2;
//                var leftX:Number = -halfW*cLocal.a + camera.focalLength*cLocal.c;
//                var leftY:Number = -halfW*cLocal.e + camera.focalLength*cLocal.g;
//                var rightX:Number = halfW*cLocal.a + camera.focalLength*cLocal.c;
//                var rightY:Number = halfW*cLocal.e + camera.focalLength*cLocal.g;
//                // Finding UV
//                var angle:Number = (Math.atan2(leftY, leftX) - Math.PI/2);
//                if (angle < 0) angle += Math.PI*2;
//                var dx:Number = rightX - leftX;
//                var dy:Number = rightY - leftY;
//                var lens:Number = Math.sqrt(dx*dx + dy*dy);
//                leftX /= lens;
//                leftY /= lens;
//                rightX /= lens;
//                rightY /= lens;
//                var uScale:Number = Math.acos(leftX*rightX + leftY*rightY)/Math.PI/2;
//                var uRight:Number = angle/Math.PI/2;
//
//                drawUnit.setFragmentConstantsFromNumbers(program.fragmentShader.getVariableIndex("cFogConsts"), 0.5*uScale, 0.5 - uRight, 0);
//                drawUnit.setTextureAt(program.fragmentShader.getVariableIndex("sFogTexture"), fogTexture._texture);
//            }
        }

        private static var lightGroup:Vector.<Light3D> = new Vector.<Light3D>();
        private static var shadowGroup:Vector.<Light3D> = new Vector.<Light3D>();

        /**
         * @private
         */
        override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector.<Light3D>, lightsLength:int, useShadow:Boolean, objectRenderPriority:int = -1):void {
            if (diffuseMap == null || normalMap == null || diffuseMap._texture == null || normalMap._texture == null) return;
            // Check if textures uploaded in to the context.
            if (opacityMap != null && opacityMap._texture == null || glossinessMap != null && glossinessMap._texture == null || specularMap != null && specularMap._texture == null || lightMap != null && lightMap._texture == null) return;

            var object:Object3D = surface.object;

            // Buffers
            var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
            var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
            var normalsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.NORMAL);
            var tangentsBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TANGENT4);

            if (positionBuffer == null || uvBuffer == null) return;

            var i:int;
            var light:Light3D;

            if (lightsLength > 0 && (_normalMapSpace == NormalMapSpace.TANGENT_RIGHT_HANDED || _normalMapSpace == NormalMapSpace.TANGENT_LEFT_HANDED)) {
                if (normalsBuffer == null || tangentsBuffer == null) return;
            }

            // Refresh programs for this context.
            if (camera.context3D != cachedContext3D) {
                cachedContext3D = camera.context3D;
                programsCache = caches[cachedContext3D];
                if (programsCache == null) {
                    programsCache = new Dictionary(false);
                    caches[cachedContext3D] = programsCache;
                }
            }

            var optionsPrograms:Array = programsCache[object.transformProcedure];
            if (optionsPrograms == null) {
                optionsPrograms = [];
                programsCache[object.transformProcedure] = optionsPrograms;
            }

            // Form groups of lights
            var groupsCount:int = 0;
            var lightGroupLength:int = 0;
            var shadowGroupLength:int = 0;
            for (i = 0; i < lightsLength; i++) {
                light = lights[i];
                if (light.shadow != null && useShadow) {
                    shadowGroup[int(shadowGroupLength++)] = light;
                } else {
                    if (lightGroupLength == 6) {
                        groups[int(groupsCount++)] = lightGroup;
                        lightGroup = new Vector.<Light3D>();
                        lightGroupLength = 0;
                    }
                    lightGroup[int(lightGroupLength++)] = light;
                }
            }
            if (lightGroupLength != 0) {
                groups[int(groupsCount++)] = lightGroup;
            }

            // Iterate groups
            var materialKey:int;
            var program:PlaneCutMaterialProgram;
            var omniLightCount:int = 0;
            var directionalLightCount:int = 0;
            var spotLightCount:int = 0;

            if (groupsCount == 0 && shadowGroupLength == 0) {
                // There is only Ambient light on the scene
                // Form key
                materialKey = ((lightMap != null) ? LIGHT_MAP_BIT : 0) | ((glossinessMap != null) ? GLOSSINESS_MAP_BIT : 0) | ((specularMap != null) ? SPECULAR_MAP_BIT : 0);

                if (opaquePass && alphaThreshold <= alpha) {
                    if (alphaThreshold > 0) {
                        // Alpha test
                        // use opacityMap if it is presented
                        program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 1, null, 0, true, null);
                        addDrawUnits(program, camera, surface, geometry, opacityMap, null, 0, true, null, true, false, objectRenderPriority);
                    } else {
                        // do not use opacityMap at all
                        program = getProgram(object, optionsPrograms, camera, materialKey, null, 0, null, 0, true, null);
                        addDrawUnits(program, camera, surface, geometry, null, null, 0, true, null, true, false, objectRenderPriority);
                    }
                }
                // Transparent pass
                if (transparentPass && alphaThreshold > 0 && alpha > 0) {
                    // use opacityMap if it is presented
                    if (alphaThreshold <= alpha && !opaquePass) {
                        // Alpha threshold
                        program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 2, null, 0, true, null);
                        addDrawUnits(program, camera, surface, geometry, opacityMap, null, 0, true, null, false, true, objectRenderPriority);
                    } else {
                        // There is no Alpha threshold or check z-buffer by previous pass
                        program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 0, null, 0, true, null);
                        addDrawUnits(program, camera, surface, geometry, opacityMap, null, 0, true, null, false, true, objectRenderPriority);
                    }
                }
            } else {
                var j:int;
                var isFirstGroup:Boolean = true;
                for (i = 0; i < groupsCount; i++) {
                    lightGroup = groups[i];
                    lightGroupLength = lightGroup.length;

                    // Group of lights without shadow
                    // Form key
                    materialKey = (isFirstGroup) ? ((lightMap != null) ? LIGHT_MAP_BIT : 0) : 0;
                    materialKey |= (_normalMapSpace << NORMAL_MAP_SPACE_OFFSET) | ((glossinessMap != null) ? GLOSSINESS_MAP_BIT : 0) | ((specularMap != null) ? SPECULAR_MAP_BIT : 0);
                    for (j = 0; j < lightGroupLength; j++) {
                        light = lightGroup[j];
                        if (light is OmniLight) omniLightCount++; else if (light is DirectionalLight) directionalLightCount++; else if (light is SpotLight) spotLightCount++;
                    }
                    materialKey |= omniLightCount << OMNI_LIGHT_OFFSET;
                    materialKey |= directionalLightCount << DIRECTIONAL_LIGHT_OFFSET;
                    materialKey |= spotLightCount << SPOT_LIGHT_OFFSET;

                    // Create program and drawUnit for group
                    // Opaque pass
                    if (opaquePass && alphaThreshold <= alpha) {
                        if (alphaThreshold > 0) {
                            // Alpha test
                            // use opacityMap if it is presented
                            program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 1, lightGroup, lightGroupLength, isFirstGroup, null);
                            addDrawUnits(program, camera, surface, geometry, opacityMap, lightGroup, lightGroupLength, isFirstGroup, null, true, false, objectRenderPriority);
                        } else {
                            // do not use opacityMap at all
                            program = getProgram(object, optionsPrograms, camera, materialKey, null, 0, lightGroup, lightGroupLength, isFirstGroup, null);
                            addDrawUnits(program, camera, surface, geometry, null, lightGroup, lightGroupLength, isFirstGroup, null, true, false, objectRenderPriority);
                        }
                    }
                    // Transparent pass
                    if (transparentPass && alphaThreshold > 0 && alpha > 0) {
                        // use opacityMap if it is presented
                        if (alphaThreshold <= alpha && !opaquePass) {
                            // Alpha threshold
                            program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 2, lightGroup, lightGroupLength, isFirstGroup, null);
                            addDrawUnits(program, camera, surface, geometry, opacityMap, lightGroup, lightGroupLength, isFirstGroup, null, false, true, objectRenderPriority);
                        } else {
                            // There is no Alpha threshold or check z-buffer by previous pass
                            program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 0, lightGroup, lightGroupLength, isFirstGroup, null);
                            addDrawUnits(program, camera, surface, geometry, opacityMap, lightGroup, lightGroupLength, isFirstGroup, null, false, true, objectRenderPriority);
                        }
                    }
                    isFirstGroup = false;
                    lightGroup.length = 0;
                }

                if (shadowGroupLength > 0) {
                    // Group of ligths with shadow
                    // For each light we will create new drawUnit
                    for (j = 0; j < shadowGroupLength; j++) {

                        light = shadowGroup[j];
                        // Form key
                        materialKey = (isFirstGroup) ? ((lightMap != null) ? LIGHT_MAP_BIT : 0) : 0;
                        materialKey |= (_normalMapSpace << NORMAL_MAP_SPACE_OFFSET) | ((glossinessMap != null) ? GLOSSINESS_MAP_BIT : 0) | ((specularMap != null) ? SPECULAR_MAP_BIT : 0);
                        materialKey |= light.shadow.type << SHADOW_OFFSET;
                        if (light is OmniLight) materialKey |= 1 << OMNI_LIGHT_OFFSET; else if (light is DirectionalLight) materialKey |= 1 << DIRECTIONAL_LIGHT_OFFSET; else if (light is SpotLight) materialKey |= 1 << SPOT_LIGHT_OFFSET;

                        // Для группы создаем программу и дроуюнит
                        // Opaque pass
                        if (opaquePass && alphaThreshold <= alpha) {
                            if (alphaThreshold > 0) {
                                // Alpha test
                                // use opacityMap if it is presented
                                program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 1, null, 0, isFirstGroup, light);
                                addDrawUnits(program, camera, surface, geometry, opacityMap, null, 0, isFirstGroup, light, true, false, objectRenderPriority);
                            } else {
                                // do not use opacityMap at all
                                program = getProgram(object, optionsPrograms, camera, materialKey, null, 0, null, 0, isFirstGroup, light);
                                addDrawUnits(program, camera, surface, geometry, null, null, 0, isFirstGroup, light, true, false, objectRenderPriority);
                            }
                        }
                        // Transparent pass
                        if (transparentPass && alphaThreshold > 0 && alpha > 0) {
                            // use opacityMap if it is presented
                            if (alphaThreshold <= alpha && !opaquePass) {
                                // Alpha threshold
                                program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 2, null, 0, isFirstGroup, light);
                                addDrawUnits(program, camera, surface, geometry, opacityMap, null, 0, isFirstGroup, light, false, true, objectRenderPriority);
                            } else {
                                // There is no Alpha threshold or check z-buffer by previous pass
                                program = getProgram(object, optionsPrograms, camera, materialKey, opacityMap, 0, null, 0, isFirstGroup, light);
                                addDrawUnits(program, camera, surface, geometry, opacityMap, null, 0, isFirstGroup, light, false, true, objectRenderPriority);
                            }
                        }
                        isFirstGroup = false;
                    }
                }
                shadowGroup.length = 0;
            }
            groups.length = 0;
        }

        /**
         * @inheritDoc
         */
        override public function clone():Material {
            var res:PlaneCutMaterial = new PlaneCutMaterial(diffuseMap, normalMap, specularMap, glossinessMap, opacityMap);
            res.clonePropertiesFrom(this);
            return res;
        }

        /**
         * @inheritDoc
         */
        override protected function clonePropertiesFrom(source:Material):void {
            super.clonePropertiesFrom(source);
            var sMaterial:PlaneCutMaterial = PlaneCutMaterial(source);
            glossiness = sMaterial.glossiness;
            specularPower = sMaterial.specularPower;
            _normalMapSpace = sMaterial._normalMapSpace;
            lightMap = sMaterial.lightMap;
            lightMapChannel = sMaterial.lightMapChannel;
            planeResult = sMaterial.planeResult;
        }

    }
//}

import alternativa.engine3d.materials.ShaderProgram;
import alternativa.engine3d.materials.compiler.Linker;

import flash.display3D.Context3D;

class PlaneCutMaterialProgram extends ShaderProgram {

    public var aPosition:int = -1;
    public var aUV:int = -1;
    public var aUV1:int = -1;
    public var aNormal:int = -1;
    public var aTangent:int = -1;
    public var cProjMatrix:int = -1;
    public var cCameraPosition:int = -1;
    public var cAmbientColor:int = -1;
    public var cSurface:int = -1;
    public var cThresholdAlpha:int = -1;
    public var cPlane:int = -1;
    public var sDiffuse:int = -1;
    public var sOpacity:int = -1;
    public var sBump:int = -1;
    public var sGlossiness:int = -1;
    public var sSpecular:int = -1;
    public var sLightMap:int = -1;

    public var cPosition:Vector.<int>;
    public var cRadius:Vector.<int>;
    public var cAxis:Vector.<int>;
    public var cColor:Vector.<int>;

    public function PlaneCutMaterialProgram(vertex:Linker, fragment:Linker, numLigths:int) {
        super(vertex, fragment);

        cPosition = new Vector.<int>(numLigths);
        cRadius = new Vector.<int>(numLigths);
        cAxis = new Vector.<int>(numLigths);
        cColor = new Vector.<int>(numLigths);
    }

    override public function upload(context3D:Context3D):void {
        super.upload(context3D);

        aPosition = vertexShader.findVariable("aPosition");
        aUV = vertexShader.findVariable("aUV");
        aUV1 = vertexShader.findVariable("aUV1");
        aNormal = vertexShader.findVariable("aNormal");
        aTangent = vertexShader.findVariable("aTangent");
        cProjMatrix = vertexShader.findVariable("cProjMatrix");
        cCameraPosition = vertexShader.findVariable("cCameraPosition");
        

        cAmbientColor = fragmentShader.findVariable("cAmbientColor");
        cSurface = fragmentShader.findVariable("cSurface");
        cPlane = fragmentShader.findVariable("cPlane");
        cThresholdAlpha = fragmentShader.findVariable("cThresholdAlpha");
        sDiffuse = fragmentShader.findVariable("sDiffuse");
        sOpacity = fragmentShader.findVariable("sOpacity");
        sBump = fragmentShader.findVariable("sBump");
        sGlossiness = fragmentShader.findVariable("sGlossiness");
        sSpecular = fragmentShader.findVariable("sSpecular");
        sLightMap = fragmentShader.findVariable("sLightMap");

        var count:int = cPosition.length;
        for (var i:int = 0; i < count; i++) {
            cPosition[i] = fragmentShader.findVariable("c" + i + "Position");
            cRadius[i] = fragmentShader.findVariable("c" + i + "Radius");
            cAxis[i] = fragmentShader.findVariable("c" + i + "Axis");
            cColor[i] = fragmentShader.findVariable("c" + i + "Color");
        }
        

    }
}


//package engine3d.utils 
//{
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.core.Transform3D;
    import alternativa.engine3d.core.VertexAttributes;
    import alternativa.engine3d.objects.Joint;
    import alternativa.engine3d.objects.Mesh;
    import alternativa.engine3d.objects.Surface;
    import alternativa.engine3d.resources.Geometry;
    import flash.display.BitmapData;
    import flash.geom.Matrix3D;
    import flash.geom.Point;
    import flash.geom.Vector3D;
    import flash.utils.Dictionary;
    import alternativa.engine3d.alternativa3d;
    use namespace alternativa3d;
    /**
     * ...
     * @author Glenn Ko
     */
    //public
    class GeometryTools 
    {
        
        public static function displaceGeometry(map:BitmapData, geometry:Geometry, heightMult:Number):void
        {
            var positions:Vector.<Number> = geometry.getAttributeValues(VertexAttributes.POSITION);
            var uvs:Vector.<Number> = geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
            
            var i:int = 0;
            var len:int = positions.length;
            var c:int = 0;
            var channelMult:Number = 1 / 255;
            while ( i < len) {
                var u:Number = uvs[c];
                var v:Number = uvs[c + 1];
                var pixelColor:uint = map.getPixel( int(u * map.width), int(v * map.height) );
                
                pixelColor &= 0x0000FF;
                
                positions[i + 2] =pixelColor * channelMult * heightMult;
                i += 3;
                c += 2;
            }
        
            geometry.setAttributeValues(VertexAttributes.POSITION, positions);
                geometry.calculateNormals();
            //geometry.calculateTangents(0);
            
        }
        
        public static function resetEuler(obj:Object3D):void {
            obj._x = 0;
            obj._y = 0;
            obj._z = 0;
            
            obj._rotationX = 0;
            obj._rotationY = 0;
            obj._rotationZ = 0;
            
            obj._scaleX = 1;
            obj._scaleY = 1;
            obj._scaleZ = 1;
            
            obj.transformChanged = true;
        }
        
        public static function transformGeometry(geometry:Geometry, t:Transform3D):void
        {
            var positions:Vector.<Number> = geometry.getAttributeValues(VertexAttributes.POSITION);
            
            
            var i:int = 0;
            var len:int = positions.length;
            
            
            while ( i < len) {
                var x:Number = positions[i];
                var y:Number = positions[i + 1];
                var z:Number = positions[i + 2];
            
                positions[i] = t.a * x + t.b * y + t.c * z + t.d;
                positions[i + 1] = t.e * x + t.f * y + t.g * z + t.h;
                positions[i + 2] = t.i * x + t.j * y + t.k * z + t.l;
                
                i += 3;
            }
        
            geometry.setAttributeValues(VertexAttributes.POSITION, positions);
                geometry.calculateNormals();
        //    geometry.calculateTangents();
        }
        
                /**
         * 3つの頂点座標から、Faceを作成し、indices、positionsに各値を登録する
         */
        public static function createTriangle(vertices:Vector.<Vector3D>, uvs:Vector.<Point>,
                                              indices:Vector.<uint>, positions:Vector.<Number>, 
                                              texcoords:Vector.<Number>, reverse:Boolean = false):void {
            if (reverse == false) {
                //三角形用の頂点を登録
                positions.push(vertices[0].x, vertices[0].y, vertices[0].z,
                               vertices[2].x, vertices[2].y, vertices[2].z,
                               vertices[1].x, vertices[1].y, vertices[1].z);
                
                //三角形用のUVを登録
                texcoords.push(uvs[0].x, uvs[0].y,uvs[2].x, uvs[2].y,uvs[1].x, uvs[1].y);
                
            } else {
                //三角形用の頂点を登録
                positions.push(vertices[0].x, vertices[0].y, vertices[0].z,
                               vertices[1].x, vertices[1].y, vertices[1].z,
                               vertices[2].x, vertices[2].y, vertices[2].z);
                
                //三角形用のUVを登録
                texcoords.push(uvs[0].x, uvs[0].y,uvs[1].x, uvs[1].y,uvs[2].x, uvs[2].y);
                
            }
            //Face用indexを登録
            var startIndex:uint = indices.length
            indices.push(startIndex + 0, startIndex + 1, startIndex + 2);
        }
        
        /**
         * 4つの頂点座標から、四角形(2つのFace)を作成し、indices、positions、uvsに各値を登録する
         */
        public static function createSquare(vertices:Vector.<Vector3D>, uvs:Vector.<Point>,
                                            indices:Vector.<uint>, positions:Vector.<Number>, 
                                            texcoords:Vector.<Number>, reverse:Boolean = false):void {
            
            if (reverse == false) {
                positions.push(    vertices[0].x, vertices[0].y, vertices[0].z,
                                vertices[3].x, vertices[3].y, vertices[3].z,
                                vertices[1].x, vertices[1].y, vertices[1].z);
                positions.push(    vertices[1].x, vertices[1].y, vertices[1].z,
                                vertices[3].x, vertices[3].y, vertices[3].z,
                                vertices[2].x, vertices[2].y, vertices[2].z);    
                //三角形用のUVを登録
                texcoords.push(uvs[0].x, uvs[0].y,uvs[3].x, uvs[3].y,uvs[1].x, uvs[1].y);
                texcoords.push(uvs[1].x, uvs[1].y,uvs[3].x, uvs[3].y,uvs[2].x, uvs[2].y);
                                
            } else {
                positions.push(    vertices[0].x, vertices[0].y, vertices[0].z,
                                vertices[1].x, vertices[1].y, vertices[1].z,
                                vertices[3].x, vertices[3].y, vertices[3].z);
                positions.push(    vertices[1].x, vertices[1].y, vertices[1].z,
                                vertices[2].x, vertices[2].y, vertices[2].z,
                                vertices[3].x, vertices[3].y, vertices[3].z);    
                //三角形用のUVを登録
                texcoords.push(uvs[0].x, uvs[0].y,uvs[1].x, uvs[1].y,uvs[3].x, uvs[3].y);
                texcoords.push(uvs[1].x, uvs[1].y, uvs[2].x, uvs[2].y, uvs[3].x, uvs[3].y);
            }
            
            //Face用indexを登録
            var startIndex:uint = indices.length
            indices.push(startIndex + 0, startIndex + 1, startIndex + 2);
            indices.push(startIndex + 3, startIndex + 4, startIndex + 5);
        
        }
        
        /**
         * 指定Meshの法線を作成します
         * @param    mesh
         */
        public static function createNormal(mesh:Mesh):void {
            //法線の有無のチェック
            if (mesh.geometry.hasAttribute(VertexAttributes.NORMAL) == false) {
                var nml:int = VertexAttributes.NORMAL;    
                mesh.geometry.addVertexStream([nml,nml,nml]);
            }
            
            var indices:Vector.<uint> = mesh.geometry.indices;
            var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
            var vartices:Vector.<Vector3D> = new Vector.<Vector3D>(positions.length / 3);
            var vNormals:Vector.<Vector3D> = new Vector.<Vector3D>(positions.length / 3);
            
            var i:int;
            var count:uint = positions.length / 3;
            for (i = 0; i < count; i++) {
                vartices[i] = new Vector3D(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
            }
            
            //面法線を求め、頂点法線に代入する
            count = indices.length;
            for (i = 0; i < count; i += 3) {
                var normal:Vector3D = calcNormal(vartices[indices[i]], vartices[indices[i + 1]], vartices[indices[i + 2]]);
                vNormals[indices[i]] = normal;
                vNormals[indices[i + 1]] = normal;
                vNormals[indices[i + 2]] = normal;
            }
            
            var normals:Vector.<Number> = new Vector.<Number>();
            
            count = vNormals.length;
            for (i = 0; i < count; i++) {
                if (vNormals[i]) {
                    normals.push(vNormals[i].x, vNormals[i].y, vNormals[i].z);
                }
            }
            
            mesh.geometry.setAttributeValues(VertexAttributes.NORMAL, normals);
        }
        
        /**
         * 面法線の計算
         * 三つの頂点座標からなる三角ポリゴンの法線を計算し返します
         */
        public static function calcNormal(a:Vector3D, b:Vector3D, c:Vector3D):Vector3D {
            var v1:Vector3D = b.subtract(a);
            var v2:Vector3D = c.subtract(a);
            var v3:Vector3D = v1.crossProduct(v2);
            //var v3:Vector3D = cross(v1,v2);
            v3.normalize();
            return (v3);
        }
        

        
        /**
         * 指定Meshの法線をSmoothShadingにします
         * @param    mesh
         */
        public static function smoothShading(mesh:Mesh,separateSurface:Boolean=false,threshold:Number=0.000001):void {
            
            var indices:Vector.<uint> = mesh.geometry.indices;
            var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
            var normals:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.NORMAL);
            
            var vartices:Vector.<Vector3D> = new Vector.<Vector3D>(positions.length / 3);
            var vNormals:Vector.<Vector3D> = new Vector.<Vector3D>(normals.length / 3);
            
            var vertexDictionary:Dictionary = new Dictionary()
            var exVertex:ExtraVertex;
            
            //サーフェースごとに判断する

            for (var s:uint = 0; s < mesh.numSurfaces; s++ ) {
                var side:String = (separateSurface) ? s.toString() : '';
                for (var n:uint = 0; n < mesh.getSurface(s).numTriangles * 3; n++) {
                    var i:uint = indices[n+mesh.getSurface(s).indexBegin];
                    vartices[i] = new Vector3D(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
                    //誤差を丸める
                    vartices[i].x = int(vartices[i].x / threshold) * threshold;
                    vartices[i].y = int(vartices[i].y / threshold) * threshold;
                    vartices[i].z = int(vartices[i].z / threshold) * threshold;                
                    vNormals[i] = new Vector3D(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
                    //誤差を丸める
                    vNormals[i].x = int(vNormals[i].x / threshold) * threshold;
                    vNormals[i].y = int(vNormals[i].y / threshold) * threshold;
                    vNormals[i].z = int(vNormals[i].z / threshold) * threshold;
                    
                    //同じ頂点を集める
                    //ただし、表裏がある場合があるので法線の方向もチェックする
                    if (vertexDictionary[vartices[i].toString()+'_'+side]) {
                        exVertex = vertexDictionary[vartices[i].toString()+'_'+side]
                        if (exVertex.normals[vNormals[i].toString()+'_'+side] == null) {
                            exVertex.normals[vNormals[i].toString()+'_'+side] = vNormals[i];
                        }
                        exVertex.indices.push(i);

                    } else {
                        exVertex = new ExtraVertex(vNormals[i].x, vNormals[i].y, vNormals[i].z);
                        exVertex.normals[vNormals[i].toString()+'_'+side] = vNormals[i];
                        exVertex.indices.push(i)
                        vertexDictionary[vartices[i].toString()+'_'+side] = exVertex
                    }
                }                
                
            }

            //Normalの平均化
            var count:uint = 0;
            for each (exVertex in vertexDictionary) {
                var normalX:Number = 0;
                var normalY:Number = 0;
                var normalZ:Number = 0;
                count = 0
                for each (var normal:Vector3D in exVertex.normals) {
                    normalX += normal.x;
                    normalY += normal.y;
                    normalZ += normal.z;
                    count++
                }
                normal = new Vector3D(normalX / count, normalY / count, normalZ / count);
                normal.normalize();
                count = exVertex.indices.length;
                for (i = 0; i < count; i++) {
                    vNormals[exVertex.indices[i]] = normal;
                }
            }
            count = vNormals.length;
            normals = new Vector.<Number>();
            for (i = 0; i < count; i++) {
                normals.push(vNormals[i].x, vNormals[i].y, vNormals[i].z);
            }
            
            mesh.geometry.setAttributeValues(VertexAttributes.NORMAL, normals);
        }
        
        
        /**
         * 指定MeshのUVをVertexのxyから仮に作成する
         * @param    mesh
         */
        public static function createUv(mesh:Mesh):void {
            if (mesh.geometry.hasAttribute(VertexAttributes.TEXCOORDS[0]) == false) {
                var tex:int = VertexAttributes.TEXCOORDS[0];    
                mesh.geometry.addVertexStream([tex,tex]);
            }
            mesh.calculateBoundBox()
            var width:Number = mesh.boundBox.maxX - mesh.boundBox.minX
            var length:Number = mesh.boundBox.maxZ - mesh.boundBox.minZ
            
            var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
            var texcoords:Vector.<Number> = new Vector.<Number>;
            var i:int;
            for (i = 0; i < positions.length; i += 3) {
                texcoords.push((positions[i] - mesh.boundBox.minX) / width, (positions[i + 2] - mesh.boundBox.minZ) / length);
            }

            mesh.geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
        }
        
        
        
        /**
         * 指定MeshのTangentを作成する
         * @param    mesh
         */
        static public function createTangent(mesh:Mesh):void {
            //接線有無のチェック


            
            
            var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
            var texcoords:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
            var normals:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.NORMAL);
            
            var indices:Vector.<uint> = mesh.geometry.indices;
            var vertices:Vector.<Vector3D> = new Vector.<Vector3D>;
            var uvs:Vector.<Point> = new Vector.<Point>;
            var vNormals:Vector.<Vector3D> = new Vector.<Vector3D>;
            
            var i:int;
            for (i = 0; i < positions.length; i += 3) {
                vertices.push(new Vector3D(positions[i], positions[i + 1], positions[i + 2]));
            }
            for (i = 0; i < texcoords.length; i += 2) {
                uvs.push(new Point(texcoords[i], texcoords[i + 1]));
            }
            for (i = 0; i < normals.length; i += 3) {
                vNormals.push(new Vector3D(normals[i], normals[i + 1], normals[i + 2]));
            }
            
            var tangents:Vector.<Number> = calcTangent(mesh.geometry.indices, vertices, uvs, vNormals);
            
            var geometry:Geometry = new Geometry();
            //if (mesh.geometry.hasAttribute(VertexAttributes.TANGENT4) == false) {
            var tan:int = VertexAttributes.TANGENT4;
            var pos:int = VertexAttributes.POSITION;
            var nor:int = VertexAttributes.NORMAL;
            var tex:int = VertexAttributes.TEXCOORDS[0];
            var attribute:Array = [
                pos, pos, pos,
                nor, nor, nor,
                tan, tan, tan, tan,
                tex, tex
            ]
            geometry.addVertexStream(attribute)
            //}                
            geometry.numVertices = mesh.geometry.numVertices
            geometry.indices = mesh.geometry.indices;
            geometry.setAttributeValues(VertexAttributes.POSITION, positions);
            geometry.setAttributeValues(VertexAttributes.NORMAL, normals);
            geometry.setAttributeValues(VertexAttributes.TANGENT4, tangents);
            geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);

            mesh.geometry = geometry;
        }
        
        
        /**
         * 複数のMeshを結合し、1つのMeshにします
         * @param    meshs
         * @return
         */
        public static function bindMeshs(meshs:Vector.<Mesh>):Mesh {
            var count:uint = meshs.length;
            
            var indices:Vector.<uint> = new Vector.<uint>();
            var positions:Vector.<Number> = new Vector.<Number>();
            var texcoords:Vector.<Number> = new Vector.<Number>();
            
            var nextIndex:uint = 0;
            var nextPosition:uint = 0;
            var mesh:Mesh = meshs[i];
            var i:int
            var j:int
            for (i = 0; i < count; i++) {
                mesh = meshs[i];
                var tempPositions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
                mesh.matrix.transformVectors(tempPositions, tempPositions);
                positions = positions.concat(tempPositions);
                texcoords = texcoords.concat(mesh.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]));
                
                var tempIndices:Vector.<uint> = mesh.geometry.indices;            
                var indexCount:uint = tempIndices.length
                for (j = 0; j < indexCount; j++) {
                    tempIndices[j] += nextIndex;
                }
                indices = indices.concat(tempIndices);
                nextIndex += tempPositions.length/3
            }

            var geometry:Geometry = new Geometry();
            
            var attributes:Array = [];
            attributes[0] = VertexAttributes.POSITION;
            attributes[1] = VertexAttributes.POSITION;
            attributes[2] = VertexAttributes.POSITION;
            attributes[3] = VertexAttributes.TEXCOORDS[0];
            attributes[4] = VertexAttributes.TEXCOORDS[0];
            
            geometry.addVertexStream(attributes);
            
            geometry.numVertices = positions.length/3;
            
            geometry.setAttributeValues(VertexAttributes.POSITION, positions);
            geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
            
            geometry.indices = indices;            
            
            var result:Mesh = new Mesh()
            result.geometry = geometry;
            
            //サーフェースのコピー
            var indexBegin:uint = 0
            for (i = 0; i < count; i++) {
                mesh = meshs[i];
                for (j = 0; j < mesh.numSurfaces; j++) {
                    var surface:Surface = mesh.getSurface(j);
                    result.addSurface(surface.material, surface.indexBegin+indexBegin, surface.numTriangles)
                }
                indexBegin = surface.indexBegin+indexBegin + surface.numTriangles * 3;
            }
            
            //normal再計算
            createNormal(result);
            return result;
        }
        
        
        /**
         * Cylinder、Cone、Dome、RoundMesh等を合成した、MeshのSurfaceを合成します
         * UVのV値のみ更新されます
         * 
         * 頂点情報の高さ(Z座標)で判断します
         * 
         */
        public static function repairRoundSurface(mesh:Mesh):Mesh {
            var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
            var texcoords:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
            var count:int = positions.length / 3
            //全体の高さを割り出す
            var minY:Number=0
            var maxY:Number=0
            for (var i:int = 0; i < count; i++) {
                if (minY > positions[i * 3 + 2])
                    minY = positions[i * 3 + 2];
                if (maxY < positions[i * 3 + 2])
                    maxY = positions[i * 3 + 2];
            }
            
            var height:Number = maxY - minY;
            for (i = 0; i < count; i++) {
                texcoords[i * 2 + 1] = (positions[i * 3 + 2] - minY) / height;
            }
            mesh.geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
            var result:Mesh = new Mesh();
            result.geometry = mesh.geometry
            result.addSurface(null, 0, positions.length / 9);
            return result;
        }
        
        
        /**
         * サーフェースのコピー
         * @param    origin
         * @param    mesh
         */
        public static function copySurface(origin:Mesh, mesh:Mesh):void {
            for (var i:uint = 0; i < origin.numSurfaces; i++) {
                var surface:Surface = origin.getSurface(i);
                mesh.addSurface(surface.material, surface.indexBegin, surface.numTriangles)
            }
        }

        
        /**
         * TANGENT4を再計算
         * @param    indices
         * @param    vertex
         * @param    uvs
         * @param    normals
         * @return
         */
        static public function calcTangent(indices:Vector.<uint>, vertices:Vector.<Vector3D>, uvs:Vector.<Point>, normals:Vector.<Vector3D>):Vector.<Number> {
            var tangent:Vector.<Number> = new Vector.<Number>;
            var numTriangle:int = indices.length / 3;
            var numVertex:int = vertices.length;
            
            var tan1:Vector.<Vector3D> = new Vector.<Vector3D>;
            var tan2:Vector.<Vector3D> = new Vector.<Vector3D>;
            
            var i:int;
            for (i = 0; i < vertices.length; i++) {
                tan1.push(new Vector3D());
                tan2.push(new Vector3D());
            }
            
            var max:int = indices.length;
            for (i = 0; i < max; i += 3) {
                var i1:Number = indices[i];
                var i2:Number = indices[i + 1];
                var i3:Number = indices[i + 2];
                
                var v1:Vector3D = vertices[i1];
                var v2:Vector3D = vertices[i2];
                var v3:Vector3D = vertices[i3];
                
                var w1:Point = uvs[i1];
                var w2:Point = uvs[i2];
                var w3:Point = uvs[i3];
                
                var x1:Number = v2.x - v1.x;
                var x2:Number = v3.x - v1.x;
                var y1:Number = v2.y - v1.y;
                var y2:Number = v3.y - v1.y;
                var z1:Number = v2.z - v1.z;
                var z2:Number = v3.z - v1.z;
                
                var s1:Number = w2.x - w1.x;
                var s2:Number = w3.x - w1.x;
                var t1:Number = w2.y - w1.y;
                var t2:Number = w3.y - w1.y;
                
                var r:Number = 1 / (s1 * t2 - s2 * t1);
                var sdir:Vector3D = new Vector3D((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
                var tdir:Vector3D = new Vector3D((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
                
                tan1[i1].incrementBy(sdir);
                tan1[i2].incrementBy(sdir);
                tan1[i3].incrementBy(sdir);
                
                tan2[i1].incrementBy(tdir);
                tan2[i2].incrementBy(tdir);
                tan2[i3].incrementBy(tdir);
            }
            
            for (i = 0; i < numVertex; i++) {
                var n:Vector3D = normals[i];
                var t:Vector3D = tan1[i];
                var tgt:Vector3D = t.subtract(getScaled(n, dot(n, t)));
                tgt.normalize();
                var w:Number = dot(cross(n, t), tan2[i]) < 0 ? -1 : 1;
                tangent.push(tgt.x, tgt.y, tgt.z, w);
            }
            return tangent;
        }
        
        /**
         * 2つのベクトルの内積を返します。
         * (内積:2つのベクトルがどれだけ平行に近いかを示す数値)
         * ・ 1 に近いほど同じ向きで平行
         * ・ 0 に近いほど直角
         * ・-1 に近いほど逆向きで平行
         */
        static public function dot(a:Vector3D, b:Vector3D):Number {
            return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
        }
        
        /**
         * 2つのベクトルの外積を返します。
         * (外積:2つのベクトルで作られる面に垂直なベクトル(=法線)。)
         */
        static public function cross(a:Vector3D, b:Vector3D):Vector3D {
            return new Vector3D((a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x));
        }
        
        /**
         * スケーリングした新しいベクトルを取得
         * @param    v
         * @param    scale
         * @return
         */
        static public function getScaled(v:Vector3D, scale:Number):Vector3D {
            var sv:Vector3D = v.clone();
            sv.scaleBy(scale);
            return sv;
        }
        
        /**
         * Jointの位置を初期化
         * @param    joints
         */
        public static function JointBindPose(joints:Vector.<Joint>):void {
            var count:uint = joints.length;
            for (var i:uint = 0; i < count; i++) 
            {
                var joint:Joint = joints[i]
                var jointMatrix:Matrix3D = joint.concatenatedMatrix.clone();

                jointMatrix.transpose();
                var jointBindingTransform:Transform3D = new Transform3D();
                jointBindingTransform.initFromVector(jointMatrix.rawData);
                jointBindingTransform.invert();
                var matrixVector:Vector.<Number> = new Vector.<Number>();
                matrixVector.push(jointBindingTransform.a);
                matrixVector.push(jointBindingTransform.b);
                matrixVector.push(jointBindingTransform.c);
                matrixVector.push(jointBindingTransform.d);
                matrixVector.push(jointBindingTransform.e);
                matrixVector.push(jointBindingTransform.f);
                matrixVector.push(jointBindingTransform.g);
                matrixVector.push(jointBindingTransform.h);
                matrixVector.push(jointBindingTransform.i);
                matrixVector.push(jointBindingTransform.j);
                matrixVector.push(jointBindingTransform.k);
                matrixVector.push(jointBindingTransform.l);
                
                joint.setBindPoseMatrix(matrixVector);

            }
        }
        
    }

//}
import flash.geom.Vector3D;
import flash.utils.Dictionary;


    /**
     * 頂点に隣接する面法線を収集するクラス
     */

    class ExtraVertex {
        public var vertex:Vector3D;
        public var normals:Dictionary;
        public var indices:Vector.<uint>;
        public function ExtraVertex(x:Number, y:Number, z:Number) {
            vertex = new Vector3D(x, y, z);
            normals = new Dictionary();
            indices = new Vector.<uint>();
        }
        
    }