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

Saboteur Jetty

Counting number of cardgame path combinations for Saboteur version 1 and version 2. 
Includes 3d version preview soon as I have a 3d model prepared!

https://en.wikipedia.org/wiki/Saboteur_%28card_game%29
Get Adobe Flash player
by Glidias 10 Jul 2015

    Talk

    Glidias at 29 Mar 2016 22:46
    The orthogonal edge landings (first 4 bits) determine if you can place a card adjacent to another. The arcs (next 6 bits), affect the path-finding/route-availability checks by altering the route graph and also determines visually whether a "center" floor is required to support traveling through it to the other side. Any remaining bits (mainly for Saboteur 2 expansion) can be used to mark additional "features" like crystals, ladder, door, etc.
    Glidias at 29 Mar 2016 22:47
    The logic behind Saboteur Path cards is really straightforward. A single path card can be represented with a single integer bitfield value. First 4 bits indicate available orthogonal edge landings (shown in uppercase letters). (eg. North side, West side, South Side and East Side), . Last 6 bits (shown in lowercase letters) indicate any path arcs that run through the card (eg. vertical arc, horizontal arc, north-west turning arc, south-west turning arc, etc. basically the 2 straight arcs and 4 corner turning arcs). To check if a card can be validly placed, you just check the card's values (their bits) accordingly in relation to their adjacent positions. eg. http://wonderfl.net/c/hcaJ

    Tags

    Embed
/**
 * Copyright Glidias ( http://wonderfl.net/user/Glidias )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/huTi
 */

package
{
    import alternativa.engine3d.core.BoundBox;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.loaders.ParserA3D;
    import alternativa.engine3d.loaders.ParserMaterial;
    import alternativa.engine3d.loaders.TexturesLoader;
    import alternativa.engine3d.materials.FillMaterial;
    import alternativa.engine3d.objects.Surface;
    import alternativa.engine3d.resources.ExternalTextureResource;
    import alternativa.engine3d.utils.Object3DUtils;
    import com.bit101.components.Label;
    import com.bit101.components.NumericStepper;
    import com.bit101.components.TextArea;
    import com.bit101.components.VBox;
    import alternativa.engine3d.alternativa3d;
    import alternativa.engine3d.materials.TextureMaterial;
    import alternativa.engine3d.materials.VertexLightTextureMaterial;
    import alternativa.engine3d.objects.WireFrame;
    import alternativa.engine3d.objects.Mesh
    import alternativa.engine3d.primitives.GeoSphere;
    import alternativa.engine3d.primitives.Plane;
    import alternativa.engine3d.resources.BitmapTextureResource;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
     import flash.display.DisplayObject;
    import flash.events.*
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.ui.Keyboard;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;
    use namespace alternativa3d;
    /**
     * Counting number of cardgame path combinations for Saboteur version 1. 
     * Includes 3d version preview soon as I have a 3d model prepared!
     * @author Glenn Ko
     */
    public class SaboteurJetty extends Sprite
    {
        // standard mask values  (fill regions paths)
        public static const EAST:uint = (1 << 0);  
        public static const NORTH:uint = (1 << 1);
        public static const WEST:uint = (1 << 2);
        public static const SOUTH:uint = (1 << 3);
        
        public static const NORTHEAST:uint = (1 << 0);
        public static const NORTHWEST:uint = (1 << 1);
        public static const SOUTHWEST:uint = (1 << 2);
        public static const SOUTHEAST:uint = (1 << 3);

        public static const NORTH_EAST:uint = (NORTH | EAST);
        public static const NORTH_WEST:uint = (NORTH | WEST);
        public static const SOUTH_WEST:uint = (SOUTH | WEST);
        public static const SOUTH_EAST:uint = (SOUTH | EAST);
        
        public static const ARC_VERTICAL:uint = (1 << 0);
        public static const ARC_HORIZONTAL:uint = (1 << 1);
        public static const ARC_NORTH_EAST:uint = (1 << 2);
        public static const ARC_NORTH_WEST:uint = (1 << 3);
        public static const ARC_SOUTH_WEST:uint = (1 << 4);
        public static const ARC_SOUTH_EAST:uint = (1 << 5);
        
        public static const ARC_MASK:uint =  ~15;  
        static public const ARC_SHIFT:uint = 4;
    
        // predicted: 2^5 (standard 90deg east,north,west,south,center mask) = 32
        // + 8 (diagonal steer bendey cards, 2 steering mirrors and 6 with-orphan portions included)
        // + 6 (vertical flip cases of diagonal steer bendey cards above 8-2=6, since 2 steering mirrors can't flip vertically) +
        // + 6 ( horizontal and vertical striaght road with stray bit at side) 
        // + 4 (t junction with orphan edge bit) 
        // -1  (empty standard case) 
        // -1 (standard center cell only filled ) = 54
        // -4  (standard single edge with center filled) (merely deeper end, achieves same purpose)
        // = 50!
        
        private var combinations:Vector.<uint> = new Vector.<uint>();
        
        
         private var _template:Template
        private const RADIAN:Number = Math.PI / 180;
        
        private var ASSET_PATH:String;
        
        public function SaboteurJetty() 
        {
           Wonderfl.disable_capture();
            
            var localMode:Boolean = (loaderInfo.url.indexOf("file://") >= 0);
            localMode = false;
            
            ASSET_PATH =  (localMode ? "" : "http://glidias.github.io/Asharena/") + "assets/";
            
            collectNumCombinations(); 

            
            setup3DView();
            
            _previewJetty = new PreviewJetty()
             _previewJetty.x = 10;
            _previewJetty.y = 10;
            addChild(_previewJetty);
            createStepper();
            
            
            var loader:URLLoader = new URLLoader();
            loader.dataFormat = URLLoaderDataFormat.BINARY;
            loader.addEventListener(Event.COMPLETE, onURLBinaryLoadComplete);
            loader.load( new URLRequest(ASSET_PATH + "models/jetties/bridge_all.a3d")  );
        
        }
        
        private function onURLBinaryLoadComplete(e:Event):void 
        {
            var loader:URLLoader = (e.currentTarget as URLLoader);
            handleModelLoaded( loader.data );
        }
        
        
        private function handleModelLoaded(byteArray:ByteArray):void {
            var parserA3D:ParserA3D = new ParserA3D();
            
            parserA3D.parse( byteArray);
            
             var objects:Vector.<Object3D> = parserA3D.objects;
            var sLen:int = objects.length;
            var sk:Mesh;
            var textures:Vector.<ExternalTextureResource> = new Vector.<ExternalTextureResource>(); //create a vector ExternalTextureResource
            var diffuse:ExternalTextureResource = new ExternalTextureResource(ASSET_PATH + "models/jetties/001.jpg");
            var dummy:BitmapTextureResource = new BitmapTextureResource(new BitmapData(2, 2, false, 0xFF0000));
            var injectMaterial:StandardMaterial = new StandardMaterial(diffuse, normalResource);
            dummy.upload(_template.stage3D.context3D);
            diffuse.upload(_template.stage3D.context3D);
            
            textures.push(diffuse);
            
            for (var s:int = 0; s < sLen; s++) {
                sk = objects[s] as Mesh;
            
                if (sk == null) continue;
                    sk.geometry.calculateNormals();
                    sk.geometry.calculateTangents(0);
                    
                    sk.geometry.upload(_template.stage3D.context3D);
        
                for (var i:int = 0; i < sk.numSurfaces; i++){ //cycle through all surface
                    var surface:Surface = sk.getSurface(i); //get the current surface
                    var material:ParserMaterial = surface.material as ParserMaterial; //a material property, we obtain ParserMaterial (for materials in Section 1.3)
                    /*
                    if (material != null) { //if the material is there, not null
                        

                        var diffuse:ExternalTextureResource = material.textures["diffuse"]; //Create TextureResource-is the base class for all texture resources
                        if (diffuse != null){ //if there is texture
                            textures.push(diffuse); //add a vector with ExternalTextureResource
                            diffuse.url = ASSET_PATH + "models/jetties/001.jpg"; //skinTexturePath + diffuse.url;
                            surface.material = injectMaterial = new StandardMaterial(diffuse); //and assign the surface
                            
                        }
                    }
                    */
                    
                    surface.material = injectMaterial; // new FillMaterial(0xFF0000, 1);
            
                }
                    //    _template.scene.addChild(sk);
                //sk.boundBox = null;
            }
            var texturesLoader:TexturesLoader = new TexturesLoader(_template.stage3D.context3D);
            _textureLoader = texturesLoader;
            texturesLoader.loadResources(textures); //load the textures in the context
        
            _template.controlObject.addChild(_jettyContainer=parserA3D.hierarchy[0]);
                
            var bounds:BoundBox = Object3DUtils.calculateHierarchyBoundBox(_jettyContainer, _floor);
            _floor.z = _jettyContainer.boundBox.minZ;
            
            bounds.minX += 1;
            bounds.minY += 1;
            
            bounds.maxX -= 1;
            bounds.maxY -= 1;
            
            _floor.scaleX = bounds.minX  / _floor.boundBox.minX;
            _floor.scaleY = bounds.minY  / _floor.boundBox.minY;
        
            _template.rootControl.scaleX = 4;
            _template.rootControl.scaleY = 4;
            _template.rootControl.scaleZ = 4;
            
            onStep(null, _stepper);
            
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        }
        
        private var _scrnie:Bitmap;
        private function onKeyDown(e:KeyboardEvent):void 
        {
            if (e.keyCode === Keyboard.F6) {
                
                  _template.takeScreenshot(screenieMethod);
                
                
            }
            else if (e.keyCode === Keyboard.F7) {
                  _scrnie= _template.takeScreenshot(screenieMethod2);
            }
        }
        
        private function screenieMethod():Boolean 
        {
        //    Wonderfl.capture(); //
            return true;
        
        }
        
         private function screenieMethod2():Boolean 
        {

            stage.addEventListener(MouseEvent.CLICK, removeScreenie);
            return false;
        }
        
        private function removeScreenie(e:Event=null):void {
            if (_scrnie == null) return;
            stage.removeEventListener(MouseEvent.CLICK, removeScreenie);
            _scrnie.parent.removeChild(_scrnie);
            _scrnie = null;
        }

        
        private function setup3DView():void 
        {
            _template = new Template();
            _template.addEventListener(Template.VIEW_CREATE, initialize);
            addChild(_template);
        }
        
        private function initialize(event:Event):void {
            _template.removeEventListener(Template.VIEW_CREATE, initialize);
            //マテリアル用のリソースの用意
            var bmd:BitmapData = new BitmapData(256, 256, true, 0xFF000000);
            bmd.perlinNoise(64, 64, 1, 1, true , true);
            var textureResource:BitmapTextureResource = new BitmapTextureResource(bmd);

            
            //Textureの作成
            var diffuseMap:BitmapData = new BitmapData(16, 16, false, 0xFF6666);
            var normalMap:BitmapData = new BitmapData(16, 16, false, 0x8080FF);

            //マテリアルの作成
            var bitmapResource:BitmapTextureResource = new BitmapTextureResource(diffuseMap);
            normalResource = new BitmapTextureResource(normalMap);
            var materialA:TextureMaterial = new TextureMaterial(bitmapResource);
            var materialB:TextureMaterial = new TextureMaterial(normalResource);
            //var material:TextureMaterial = new TextureMaterial(bitmapResource);
            
            var plane:Plane = new Plane(100, 100, 1, 1, true, false, materialA, materialB)
            
            _template.controlObject.addChild(plane)
            _template.initialize();
            _floor = plane;
        }
        
        private function createStepper():void 
        {
            var stepper:NumericStepper;
            
            
            var vLayout:VBox = new VBox(this, 128);
            vLayout.x = 128;
            
            var label:Label = new Label(vLayout, 0, 0, (combinations.length) + " found.");
            label.blendMode = "invert";
            stepper = new NumericStepper(vLayout, 0, 0, onStep);
            stepper.minimum = 0;
            stepper.maximum = combinations.length - 1;
            
            stepper.value = 0;
            
        
            debugField = new TextArea(vLayout);
            
                onStep(null, stepper);
                _stepper = stepper;
            
        }
        
        private function onStep(e:Event, stepper:NumericStepper=null):void 
        {
            var n:int;
            
            var stepper:NumericStepper =  e ? (e.currentTarget as NumericStepper) : stepper;
            var value:uint = combinations[ stepper.value  ];
            
            var debugStr:String = "";
            debugStr += (value & EAST) ? "E " : "";
            debugStr += (value & NORTH) ? "N " : "";
            debugStr += (value & WEST) ? "W " : "";
            debugStr += (value & SOUTH) ? "S " : "";
            
            var arcValue:uint = (value & ARC_MASK) >> ARC_SHIFT;
            debugStr += (arcValue & ARC_VERTICAL) ? "vert " : "";
            debugStr += (arcValue & ARC_HORIZONTAL) ? "horiz " : "";
            debugStr += (arcValue & ARC_NORTH_EAST) ? "ne " : "";
            debugStr += (arcValue & ARC_NORTH_WEST) ? "nw " : "";
            debugStr += (arcValue & ARC_SOUTH_WEST) ? "sw " : "";
            debugStr += (arcValue & ARC_SOUTH_EAST) ? "se " : "";
            
            debugField.text = debugStr;
        
            if (_previewJetty) {
                n = _previewJetty.numChildren;
                while (--n > -1) {
                    var d:DisplayObject = _previewJetty.getChildAt(n);
                    d.visible = visJetty(value, d.name, stepper.value);
                }
            }
            
            if (_jettyContainer) {
                for (var c:Object3D = _jettyContainer.childrenList; c != null; c = c.next) {
                    c.visible = visJetty(value, c.name, stepper.value);
                }
            }
        }
        

        
        private var arcEdgeDict:Dictionary = new Dictionary();
        private var _previewJetty:Sprite;
        
        private function getArcConnectionMaskValues2():Vector.<uint> {   // 12 hardcoded arc combinations
            var vec:Vector.<uint> = new <uint>[   // setup lookup table of 6 possible connections to check
            //    0,
                (ARC_VERTICAL),
                (ARC_HORIZONTAL),
                (ARC_VERTICAL | ARC_HORIZONTAL),
                (ARC_NORTH_WEST | ARC_SOUTH_WEST),
                (ARC_NORTH_EAST | ARC_SOUTH_EAST),
                (ARC_NORTH_WEST | ARC_NORTH_EAST),   // 5 - start use 90 degree junction
                (ARC_SOUTH_WEST | ARC_SOUTH_EAST),
                (ARC_NORTH_EAST),
                (ARC_NORTH_WEST),
                (ARC_SOUTH_WEST),
                (ARC_SOUTH_EAST),   // 10 - end use 90 degree junction
                (ARC_NORTH_WEST | ARC_SOUTH_EAST),
                (ARC_SOUTH_WEST | ARC_NORTH_EAST )
            ];
            var result:uint;
            var len:int = vec.length;
            for (var i:int = 0; i < len; i++) {
                var value:uint = vec[i];
                result = 0;
                
                result |= (value & ARC_VERTICAL) ? (NORTH | SOUTH) : 0;
                result |= (value & ARC_HORIZONTAL) ? (WEST | EAST) : 0;
                result |= (value & ARC_NORTH_EAST) ? (NORTH | EAST) : 0;
                result |= (value & ARC_NORTH_WEST) ? (NORTH | WEST) : 0;
                result |= (value & ARC_SOUTH_WEST) ? (SOUTH | WEST) : 0;
                result |= (value & ARC_SOUTH_EAST) ? (SOUTH | EAST) : 0;
                
                arcEdgeDict[value] = result;
            }
            
            

            //throw new Error(vec);
            return vec;
        }
        
        
        //private var arcValueList:Array = [];  // for debuggign
        private var debugField:TextArea;
        private var _textureLoader:TexturesLoader;
        private var normalResource:BitmapTextureResource;
        private var _floor:Plane;
        private var _jettyContainer:Object3D;
        private var _stepper:NumericStepper;
        
        private function collectNumCombinations():void 
        {
            var dict:Dictionary = new Dictionary();
            var vec:Vector.<uint> = getArcConnectionMaskValues2();
            
        
            var key:uint;
            var count:uint = 0;
            
            for (var i:uint = 1; i < 16; i++) {   // go through activatable east,north,west,south edge states
                for (var a:uint = 0; a < vec.length; a++) {   // go through all activable arc combinations
                    var arcValue:uint = vec[a];
                    var mask:uint = arcEdgeDict[arcValue];
                    if ( (i  & mask) != 0 && (i & mask) === mask ) {  // case with valid connectable arc combination
                    
                        key =  (arcValue << ARC_SHIFT) | i;
                        //if (key == 0) throw new Error("WRONG1");
                        if (dict[key] == null) {
                            dict[ key] = count++;
                            combinations.push(key);
                        //    arcValueList.push(arcValue);
                        }
                    }
                    
                    // case without any connecting arc
                    ///*
                    key = i;
                    //if (key == 0) throw new Error("WRONG2");
                    if (dict[key] == null) {
                        dict[ key] = count++;
                        combinations.push(key);
                    //    arcValueList.push(0);
                    }
                //    */
                }
            }
        }
        
        
        
        
        private function visJetty(value:uint, groupName:String, index:int):Boolean {

        
            var arcValue:uint = (value & ARC_MASK) >> ARC_SHIFT;
            //if (arcValue != arcValueList[index]) throw new Error("MISMATCH!:"+arcValue + ", "+arcValueList[index]);
    
            var edgeValue:uint = value & ~ARC_MASK;
            var top90Deg:Boolean = arcValue === (ARC_NORTH_WEST | ARC_NORTH_EAST)  || (arcValue === (ARC_NORTH_EAST) && edgeValue===NORTH_EAST) || (arcValue === (ARC_NORTH_WEST) && edgeValue === NORTH_WEST);
            var bottom90Deg:Boolean = arcValue === (ARC_SOUTH_EAST | ARC_SOUTH_WEST)  || (arcValue === (ARC_SOUTH_EAST) && edgeValue === SOUTH_EAST) || (arcValue === (ARC_SOUTH_WEST) && edgeValue === SOUTH_WEST);
            var centerNarrow:Boolean = (arcValue === ARC_VERTICAL) && (edgeValue & (EAST | WEST)) != 0;
            
        //    /*
            switch (groupName) {
                case "side0":
                case "side0_posts":
                    return  ( value & EAST )!=0;
                case "side1":
                case "side1_posts":
                    return  ( value & NORTH )!=0; 
                case "side2":
                case "side2_posts":
                    return  ( value & WEST )!=0;
                case "side3":
                case "side3_posts":
                    return ( value & SOUTH )!=0;
                
                case "center":
                    return !centerNarrow && ((arcValue & (ARC_VERTICAL | ARC_HORIZONTAL))!=0) || top90Deg || bottom90Deg;
                case "center_top":
                     return  !centerNarrow && (arcValue & ARC_VERTICAL)!=0 || top90Deg; // has cut thru or T junction from bottom
                case "center_bottom":
                    return  !centerNarrow && (arcValue & ARC_VERTICAL)!=0 || bottom90Deg;   // has cut thru or T junction from bottom
            
                case "corner0_turn":  
                    return (arcValue & ARC_NORTH_EAST)!=0  &&  !top90Deg;  // and doesn't  have T junction from top
                case "corner1_turn":
                    return (arcValue & ARC_NORTH_WEST)!=0  &&  !top90Deg;
                case "corner2_turn":
                    return (arcValue & ARC_SOUTH_WEST)!=0 &&  !bottom90Deg;  // and doesn't  have T junction from bottom
                case "corner3_turn":
                    return (arcValue & ARC_SOUTH_EAST) != 0 &&  !bottom90Deg; 
                    
                case "center_narrow":
                    return centerNarrow;
        
                ///*      
                case "corner0_railing":
                    return arcValue === (ARC_VERTICAL|ARC_HORIZONTAL)  || ((arcValue & ARC_NORTH_EAST)!=0  && top90Deg);
                case "corner1_railing":
                    return arcValue === (ARC_VERTICAL|ARC_HORIZONTAL)  || ((arcValue & ARC_NORTH_WEST)!=0  && top90Deg);
                case "corner2_railing":
                    return arcValue === (ARC_VERTICAL|ARC_HORIZONTAL)  || ((arcValue & ARC_SOUTH_WEST)!=0 &&  bottom90Deg);
                case "corner3_railing":
                    return arcValue === (ARC_VERTICAL|ARC_HORIZONTAL)  || ((arcValue & ARC_SOUTH_EAST)!=0 && bottom90Deg); 
                //*/
                
                default: return false;
            }
            //    */
            ///return false;
        }
        
    }
}
import alternativa.engine3d.materials.A3DUtils;
import alternativa.engine3d.materials.LightMapMaterial;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.materials.NormalMapSpace;
import alternativa.engine3d.materials.TextureMaterial;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Shape;
import flash.display.Sprite;

class PreviewJetty extends Sprite {
    public function PreviewJetty() {
        graphics.beginFill(0x000000);
        graphics.drawRect(0, 0, 3 * 32, 5 * 32);
        
        
        createShape("side0", 2, 2);
        createShape("side1", 1, 0);
        createShape("side2", 0, 2);
        createShape("side3", 1, 4);
        
        createShape("center", 1, 2);
        createShape("center_top", 1, 1);
        createShape("center_bottom", 1, 3);
        
        createShape("corner0_turn", 4, 3, true);
         createShape("corner0_turn", 4, 2, true);
          createShape("corner0_turn", 4, 1, true);
          
        createShape("corner1_turn", 1, 3, true);
        createShape("corner1_turn", 1, 2, true);
        createShape("corner1_turn", 1, 1, true);
        
        createShape("corner2_turn", 1, 6, true);
        createShape("corner2_turn", 1, 7, true);
        createShape("corner2_turn", 1, 8, true);
        
        createShape("corner3_turn", 4, 6, true);
         createShape("corner3_turn", 4, 7, true);
          createShape("corner3_turn", 4, 8, true);
        
         createShape("center_narrow", 3, 2, true,-8);
         createShape("center_narrow", 3, 3, true,-8);
          createShape("center_narrow", 3, 4, true,-8);
           createShape("center_narrow", 3, 5, true,-8);
            createShape("center_narrow", 3, 6, true,-8);
             createShape("center_narrow", 3, 7, true,-8);
    }
    
    private function createShape(name:String, x:int, y:int, halfSize:Boolean=false, dx:Number=0, dy:Number=0):void {
        var gridSize:Number = halfSize ? 16 : 32;
        var shape:Shape = new Shape();
        shape.x = dx;
        shape.y = dy;
        shape.name = name;
        shape.graphics.lineStyle(1, 0xFFFFFF);
        shape.graphics.beginFill(0xFF0000);
        shape.graphics.drawRect(x * gridSize, y * gridSize, gridSize, gridSize);
        
        addChild(shape);
    }
    
    
}

import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.primitives.GeoSphere;
import alternativa.engine3d.controllers.SimpleObjectController;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Resource;
import alternativa.engine3d.core.View;
import alternativa.engine3d.core.VertexAttributes;
import alternativa.engine3d.core.Transform3D;

import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.objects.Surface;
import alternativa.engine3d.objects.Joint;
import alternativa.engine3d.lights.AmbientLight;
import alternativa.engine3d.lights.DirectionalLight;
import alternativa.engine3d.resources.Geometry;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.utils.Dictionary;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
import flash.geom.*;


class Template extends Sprite {
    public var rootControl:Object3D;
    public static const VIEW_CREATE:String = 'view_create'
    
    public var stage3D:Stage3D
    public 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 function Template() {
        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 onContextCreate(e:Event):void {
        stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
        //View3D(表示エリア)の作成
        var view:View = new View(stage.stageWidth, stage.stageHeight);
        view.backgroundColor = 0x666666;
        view.antiAlias = 4
        addChild(view);
        
        //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;
        
    
        //コントロールオブジェクトの作成
        rootControl = new Object3D();
    //    rootControl.rotationX = Math.PI * .5;
        scene.addChild(rootControl);
        controlObject = new Object3D()
        rootControl.addChild(controlObject);
        dispatchEvent(new Event(VIEW_CREATE));
        
        
    }
    
    
    public function initialize():void {
        for each (var resource:Resource in scene.getResources(true)) {
            trace(resource)
            resource.upload(stage3D.context3D);
        }
        
        //オブジェクト用のコントローラー(マウス操作)
        objectController = new SimpleObjectController(stage, controlObject, 100);
        objectController.mouseSensitivity = 0.2;
        
        //レンダリング
        camera.render(stage3D);
        
        addEventListener(Event.ENTER_FRAME, onRenderTick);
    }
    
    public function takeScreenshot( method:Function=null) : Bitmap  //width:int, height:int,
     {
          var view:View = camera.view;
          /*
          var oldWidth:Number = view.width;
          var oldHeight:Number = view.height;  
          view.width = width;
          view.height = height; 
          */
          view.renderToBitmap = true;
          camera.render(stage3D);
         var canvas:BitmapData =  view.canvas.clone();
         // var bitmapData:BitmapData = view.canvas.clone();
          view.renderToBitmap = false;
        //  view.width = oldWidth;
        //  view.height = oldHeight;   
            var child:Bitmap = new Bitmap(canvas);
            stage.addChildAt( child,0 );
         // take screenshot here
             if (method!= null && method() ) {
                stage.removeChild(child);
             }
          return child;
     }

    
    public function onRenderTick(e:Event):void {
        objectController.update()
        camera.render(stage3D);
    }
}


/**
 * Primitive
 */

    
class Primitive extends Mesh {
    protected const RADIAN:Number = Math.PI / 180;
    
    public var inSide:Surface = null;
    public var outSide:Surface = null;        
    protected var indices:Vector.<uint> = new Vector.<uint>();
    protected var positions:Vector.<Number> = new Vector.<Number>();
    protected var texcoords:Vector.<Number> = new Vector.<Number>();        
    
    //表用と裏用のpositionsとtexcoordsとindicesとを追加
    protected var positionsInSide:Vector.<Number> = new Vector.<Number>();
    protected var positionsOutSide:Vector.<Number> = new Vector.<Number>();
    protected var texcoordsInSide:Vector.<Number> = new Vector.<Number>();
    protected var texcoordsOutSide:Vector.<Number> = new Vector.<Number>();
    protected var indicesInSide:Vector.<uint> = new Vector.<uint>();
    protected var indicesOutSide:Vector.<uint> = new Vector.<uint>();            
    
    public function Primitive() {
        geometry = new Geometry();
        var pos:int = VertexAttributes.POSITION
        var tex:int = VertexAttributes.TEXCOORDS[0]
        geometry.addVertexStream([pos,pos,pos,tex,tex]);
    }
    
    protected function setGeometry():void {
        positions = positionsOutSide.concat(positionsInSide);
        texcoords = texcoordsOutSide.concat(texcoordsInSide);
        indices = indicesOutSide.concat();
        var indexStart:int = positionsOutSide.length / 3;
        var count:uint = indicesInSide.length;
        for (var i:uint = 0; i < count; i++) {
            indices.push(indicesInSide[i] + indexStart);
        }
        geometry.numVertices = positions.length / 3;
        geometry.setAttributeValues(VertexAttributes.POSITION, positions);
        geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
        geometry.indices = indices;
        if (indicesInSide.length) {
            inSide = this.addSurface(null, indicesOutSide.length, indicesInSide.length / 3);
        }
        if (indicesOutSide.length) {
            outSide = this.addSurface(null, 0, indicesOutSide.length / 3);
        }
        MeshUtility.createNormal(this);
    }
}




class RoundMesh extends Primitive {
    
    public function RoundMesh(lineList:Vector.<Point>, radialSegments:uint = 3,lastSegments:uint = 0,star:Number = 0, twoSide:Boolean = false, reverse:Boolean = false) {
        super();
        
        var rInterval:Number = 360 /  radialSegments;
        var heightSegments:uint = lineList.length - 1;
        var height:Number = lineList[heightSegments].y - lineList[0].y;
        
        var radian:Number
        var segmentCount:int = (lastSegments > 0) ? lastSegments : radialSegments;
        for (var i:int = 0; i < segmentCount; i++) {
            for (var j:int = 0; j < heightSegments; j++) {
                var vertices:Vector.<Vector3D> = new Vector.<Vector3D>(4);
                var uvs:Vector.<Point> = new Vector.<Point>(4);
                
                //時計周りで四角を作成していく
                var tempRadiusA:Number = (i % 2 == 1 && star > 0) ? lineList[j].x * star : lineList[j].x
                var tempRadiusB:Number = (i % 2 == 1 && star > 0) ? lineList[j+1].x * star : lineList[j+1].x
                
                radian = (rInterval * i - 90) * RADIAN;
                vertices[0] = new Vector3D(Math.cos(radian) * tempRadiusA, Math.sin(radian) * tempRadiusA, lineList[j].y-(height/2));
                vertices[3] = new Vector3D(Math.cos(radian) * tempRadiusB, Math.sin(radian) * tempRadiusB, lineList[j+1].y-(height/2));
                
                radian = (rInterval * (i + 1) - 90) * RADIAN;
                vertices[1] = new Vector3D(Math.cos(radian) * tempRadiusA, Math.sin(radian) * tempRadiusA, lineList[j].y-(height/2));
                vertices[2] = new Vector3D(Math.cos(radian) * tempRadiusB, Math.sin(radian) * tempRadiusB, lineList[j+1].y-(height/2));

                uvs[0] = new Point(1 / radialSegments * -i, 1 / heightSegments * j);
                uvs[3] = new Point(1 / radialSegments * -i, 1 / heightSegments * (j+1));
                uvs[1] = new Point(1 / radialSegments * -(i+1), 1 / heightSegments * j);
                uvs[2] = new Point(1 / radialSegments * -(i+1), 1 / heightSegments * (j+1));
                
                //Cylinderに巻きつける場合、裏表が逆になるので注意(UVも逆)
                if (reverse == false || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesOutSide, positionsOutSide, texcoordsOutSide, true)
                }
                if (reverse == true || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesInSide, positionsInSide, texcoordsInSide, false)
                }
                
            }
        }
        setGeometry();
    }
}






class Cylinder extends Primitive {
    public function Cylinder(topRadius:Number = 0, bottomRadius:Number = 50, height:Number = 100, radialSegments:uint = 3, heightSegments:uint = 1, lastSegments:uint = 0,star:Number = 0, twoSide:Boolean = false, reverse:Boolean = false) {
        
        var rInterval:Number = 360 / radialSegments;
        var hInterval:Number = height / heightSegments;
        
        var radian:Number
        var segmentCount:int = (lastSegments > 0) ? lastSegments : radialSegments;
        for (var i:int = 0; i < segmentCount; i++) {
            for (var j:int = 0; j < heightSegments; j++) {
                var vertices:Vector.<Vector3D> = new Vector.<Vector3D>(4);
                var uvs:Vector.<Point> = new Vector.<Point>(4);
                var px:Number=0;
                var py:Number = 0;
                var tmpR:Number = 0;
                var starRatio:Number = 0;
                radian = (rInterval * i - 90) * RADIAN;
                starRatio = (i % 2 == 1 && star > 0) ? star : 1;
                px = Math.cos(radian)
                py = Math.sin(radian)
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * j, starRatio);
                vertices[0] = new Vector3D(px * tmpR, py * tmpR, hInterval * j - (height / 2));
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * (j + 1), starRatio);
                vertices[3] = new Vector3D(px * tmpR, py * tmpR, hInterval * (j + 1) - (height / 2));
                
                radian = (rInterval * (i + 1) - 90) * RADIAN;
                starRatio = (i % 2 == 0 && star > 0) ? star : 1;
                px = Math.cos(radian);
                py = Math.sin(radian);
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * j, starRatio);
                vertices[1] = new Vector3D(px * tmpR, py * tmpR, hInterval * j - (height / 2));
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * (j + 1), starRatio);
                vertices[2] = new Vector3D(px * tmpR, py * tmpR, hInterval * (j + 1) - (height / 2));
                
                var u:Number = 1 / radialSegments;
                var v:Number = 1 / heightSegments;
                uvs[0] = new Point(u * -i, v * j);
                uvs[3] = new Point(u * -i, v * (j + 1));
                uvs[1] = new Point(u * -(i + 1), v * j);
                uvs[2] = new Point(u * -(i + 1), v * (j + 1));
                
                
                //Cylinderに巻きつける場合、裏表が逆になるので注意(UVも逆)
                if (reverse == false || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesOutSide, positionsOutSide, texcoordsOutSide, true)
                }
                if (reverse == true || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesInSide, positionsInSide, texcoordsInSide, false)
                }
                
            }
        }
        setGeometry()

    }
    
    /**
     * 半径を高さの比率から割り出す
     * @return
     */
    private function getRadius(topRadius:Number, bottomRadius:Number, height:Number, length:Number, starRatio:Number):Number {
        var result:Number
        var difference:Number
        var ratio:Number
        
        if (topRadius > bottomRadius) {
            difference = topRadius - bottomRadius;
            ratio = (length) ? (height - length) / height : 1
            result = (bottomRadius + (difference * ratio));
        } else {
            difference = bottomRadius - topRadius;
            ratio = (length) ? length / height : 0
            result = (topRadius + (difference * ratio));
        }
        result *= starRatio
        return result;
    }

}








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

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>();
    }
    
}



/**
 * MeshUtility
 */


use namespace alternativa3d;

class MeshUtility {
    
    public function MeshUtility() {
    
    }
    
    /**
     * 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);

        }
    }
    

}

    

/**
 * 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 alternativa.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.compiler.Linker;
    import alternativa.engine3d.materials.compiler.Procedure;
    import alternativa.engine3d.materials.compiler.VariableType;
    import alternativa.engine3d.objects.Surface;
    import alternativa.engine3d.resources.Geometry;
    import alternativa.engine3d.resources.TextureResource;

    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;

    /**
     * Supports forceRenderPriority and some basic fixes to allow rendering without vertex normals.
     * 
     * 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 StandardMaterial 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>>();

        /**
         * @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;

        // 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"
        ]);
        
        // 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");

        /**
         * @private
         */
        alternativa3d static var fallbackTextureMaterial:TextureMaterial = new TextureMaterial();
        /**
         * @private
         */
        alternativa3d static var fallbackLightMapMaterial:LightMapMaterial = new LightMapMaterial();

        /**
         * 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_RIGHT_HANDED;
         *
         * @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;
        
        
        public var forceRenderPriority:int = -1;

        /**
         * Creates a new StandardMaterial instance.
         * @param diffuseMap Diffuse map.
         * @param normalMap Normal map.
         * @param specularMap Specular map.
         * @param glossinessMap Glossiness map.
         * @param opacityMap Opacity map.
         */
        public function StandardMaterial(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):StandardMaterialProgram {
            // 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:StandardMaterialProgram = 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());

                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 StandardMaterialProgram(vertexLinker, fragmentLinker, (shadowedLight != null) ? 1 : lightsLength);
                
                program.upload(camera.context3D);
                programs[key] = program;
            }
            return program;
        }

        private function addDrawUnits(program:StandardMaterialProgram, 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 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);

                var camTransform:Transform3D = 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;
            
            objectRenderPriority = forceRenderPriority < 0 ? objectRenderPriority : forceRenderPriority;

            if (camera.context3DProperties.isConstrained) {
                // fallback to simpler material
                if (lightMap == null) {
                    fallbackTextureMaterial.diffuseMap = diffuseMap;
                    fallbackTextureMaterial.opacityMap = opacityMap;
                    fallbackTextureMaterial.alphaThreshold = alphaThreshold;
                    fallbackTextureMaterial.alpha = alpha;
                    fallbackTextureMaterial.opaquePass = opaquePass;
                    fallbackTextureMaterial.transparentPass = transparentPass;
                    fallbackTextureMaterial.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow, objectRenderPriority);
                } else {
                    fallbackLightMapMaterial.diffuseMap = diffuseMap;
                    fallbackLightMapMaterial.lightMap = lightMap;
                    fallbackLightMapMaterial.lightMapChannel = lightMapChannel;
                    fallbackLightMapMaterial.opacityMap = opacityMap;
                    fallbackLightMapMaterial.alphaThreshold = alphaThreshold;
                    fallbackLightMapMaterial.alpha = alpha;
                    fallbackLightMapMaterial.opaquePass = opaquePass;
                    fallbackLightMapMaterial.transparentPass = transparentPass;
                    fallbackLightMapMaterial.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow, objectRenderPriority);
                }
                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:StandardMaterialProgram;

            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);
                    var omniLightCount:int = 0;
                    var directionalLightCount:int = 0;
                    var spotLightCount:int = 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:StandardMaterial = new StandardMaterial(diffuseMap, normalMap, specularMap, glossinessMap, opacityMap);
            res.clonePropertiesFrom(this);
            return res;
        }

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

    }
//}

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

import flash.display3D.Context3D;

class StandardMaterialProgram 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 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 StandardMaterialProgram(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");
        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");
        }

    }
}