"Imagination is more important than knowledge." -Albert Einstein

Drawing Snowflakes: Part 1

Posted: January 7th, 2010 | Author: admin | Filed under: Uncategorized | No Comments »

So, like I said in my last post, I’m going to dedicate these few next posts to go over some of the techniques I used to create Flurrious.com.

The first thing I want to go over is the creation of the drawing application. The architecture of it is pretty simple, but crucial to making it work well, and having it be extendable and modular. My first step was to create the canvas. since a snowflake has an ice crystal formation, it has to have 6 sides.  So my first step is to draw 6 triangles that form a hexagon:

The code for this is pretty simple, looks like this:

package  
{
    import flash.display.MovieClip;
    import flash.display.Sprite;

    /**
     * @author iasseo
     */

    public class SnowFlake extends MovieClip
    {
        private const SLICES : Number = 6;
        private const RADIUS : Number = 300;
       
        private var _sliceArray : Array = new Array();
        private var _angle : Number;

        public function SnowFlake()
        {
            _angle = Math.PI / SLICES;
            createFlakeStage();
        }
       
        private function createFlakeStage() : void
        {
            for (var n : Number = 0;n < SLICES; n++)
            {
                var slice : Sprite = createSlice(_angle);
                slice.x = stage.stageWidth / 2;
                slice.y = stage.stageHeight / 2;
                slice.rotation = 360 / SLICES * n;
                _sliceArray.push(slice);
                addChild(slice);
            }
        }
       
        private function createSlice(angle : Number) : Sprite
        {
            var slice : Sprite = new Sprite();
            slice.graphics.moveTo(0, 0);
            slice.graphics.lineStyle(1,0);
            slice.graphics.beginFill(0,.1);
            slice.graphics.lineTo(Math.cos((angle)-Math.PI/2)*RADIUS, Math.sin((angle)-Math.PI/2)*RADIUS);
            slice.graphics.lineTo(Math.cos(-(angle)-Math.PI/2)*RADIUS, Math.sin(-(angle)-Math.PI/2)*RADIUS);
            slice.graphics.lineTo(0, 0);
            slice.graphics.endFill();
           
            slice.graphics.moveTo(0, 0);
            slice.graphics.lineTo(0,Math.sin((_angle)-Math.PI/2)*RADIUS);
            slice.graphics.endFill();
            return slice;
        }
    }
}

Now, even though this is my canvas which I’m using to capture the mouse events, I’m going to draw the actual shapes on a separate Sprite, so I can have better control. In order to do that, I’m going to place 6 empty Sprites in the center of the hexagon, each one rotated, just like the triangles I drew. I’ll add a new function – createShapeHolders() – to the constructor (notice that I’m disabling all MouseEvents for these ’shapes’, because I want to control them from the canvas I drew earlier). For each of the triangles (which are separate Sprites), I’m going to add a MouseEvent, which will trigger the drawing application. I’m going to add that to the ‘for loop’ in the createFlakeStage() function, and I’m also adding 3 MouseEvent functions: beginShape, renderShape and endShape.

When I click on any of the triangles now, beginShape() will save the slice I clicked on as _slice (added as a private var to the class), and trigger a MOUSE_MOVE event. Then, as I move the mouse, the renderShape() function will draw on all 6 sides of the hexagon:


CLICK TO SEE THIS IN ACTION

package  
{
    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.events.MouseEvent;

    /**
     * @author iasseo
     */

    public class SnowFlake extends MovieClip
    {
        private const SLICES : Number = 6;
        private const RADIUS : Number = 300;
       
        private var _sliceArray : Array = new Array();
        private var _angle : Number;
        private var _shapeHolders : Vector.<Sprite> = new Vector.<Sprite>();
        private var _slice : Sprite;  

        public function SnowFlake()
        {
            _angle = Math.PI / SLICES;
            createFlakeStage();
            createShapeHolders();
        }
       
        private function createShapeHolders() : void
        {
            for (var n : Number = 0;n < SLICES; n++)
            {
                var shape : Sprite = new Sprite();
                shape.mouseEnabled = false;
                shape.mouseChildren = false;
                shape.x = stage.stageWidth / 2;
                shape.y = stage.stageHeight / 2;
                _shapeHolders.push(shape);
                shape.rotation = 360 / SLICES * n;
                addChild(shape);
            }
        }

        private function createFlakeStage() : void
        {
            for (var n : Number = 0;n < SLICES; n++)
            {
                var slice : Sprite = createSlice(_angle);
                slice.x = stage.stageWidth / 2;
                slice.y = stage.stageHeight / 2;
                slice.rotation = 360 / SLICES * n;
                slice.addEventListener(MouseEvent.MOUSE_DOWN, beginShape);
                slice.alpha = 1;
                _sliceArray.push(slice);
                addChild(slice);
            }
        }
       
        private function createSlice(angle : Number) : Sprite
        {
            var slice : Sprite = new Sprite();
            slice.graphics.moveTo(0, 0);
            slice.graphics.lineStyle(1,0);
            slice.graphics.beginFill(0,.1);
            slice.graphics.lineTo(Math.cos((angle)-Math.PI/2)*RADIUS, Math.sin((angle)-Math.PI/2)*RADIUS);
            slice.graphics.lineTo(Math.cos(-(angle)-Math.PI/2)*RADIUS, Math.sin(-(angle)-Math.PI/2)*RADIUS);
            slice.graphics.lineTo(0, 0);
            slice.graphics.endFill();
           
            slice.graphics.moveTo(0, 0);
            slice.graphics.lineTo(0,Math.sin((_angle)-Math.PI/2)*RADIUS);
            slice.graphics.endFill();
            return slice;
        }
       
        private function beginShape(e : MouseEvent) : void
        {
            _slice = e.target as Sprite;
            for (var i : Number = 0;i < SLICES; i++)
            {
                _shapeHolders[i].graphics.lineStyle(1,0xFFFFFF);
                _shapeHolders[i].graphics.moveTo(_slice.mouseX, _slice.mouseY);
            }
           
            stage.addEventListener(MouseEvent.MOUSE_MOVE, renderShape);
            stage.addEventListener(MouseEvent.MOUSE_UP, endShape);
        }
       
        private function renderShape(e : MouseEvent) : void
        {
            for (var i : Number = 0;i < SLICES; i++)
            {
                _shapeHolders[i].graphics.lineTo(_slice.mouseX, _slice.mouseY);
            }
        }

        private function endShape(e : MouseEvent) : void
        {
            stage.removeEventListener(MouseEvent.MOUSE_MOVE, renderShape);
            stage.removeEventListener(MouseEvent.MOUSE_UP, endShape);
        }
    }
}

Now the only problem is that it doesn’t look like a symmetrical drawing… it basically copies the shape you’re drawing and places it in a circle. Now we need to add a reflection to the shape. So, to do that, we’ll have to add 2 Sprites to each of the ’shapes’ we created in the createShapeHolders() function. One of those shapes we’ll flip horizontally (scaleX = -1) and then, each time we trigger beginShape and renderShape, we’ll use the FP10 copyFrom() function to create a reflection effect. All we have left to do now is to hide the canvases, and start drawing! here is the result:


CLICK HERE TO SEE IT IN ACTION – CLICK AND DRAG NEAR THE CENTER OF THE STAGE

and here is the final code:

package  
{
    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.events.MouseEvent;

    /**
     * @author iasseo
     */

    public class SnowFlake extends MovieClip
    {
        private const SLICES : Number = 6;
        private const RADIUS : Number = 300;
       
        private var _sliceArray : Array = new Array();
        private var _angle : Number;
        private var _shapeHolders : Vector.<Sprite> = new Vector.<Sprite>();
        private var _slice : Sprite;  

        public function SnowFlake()
        {
            _angle = Math.PI / SLICES;
            createFlakeStage();
            createShapeHolders();
        }
       
        private function createShapeHolders() : void
        {
            for (var n : Number = 0;n < SLICES; n++)
            {
                var shape : Sprite = new Sprite();
                shape.mouseEnabled = false;
                shape.mouseChildren = false;
                shape.addChild(new Sprite());
                shape.addChild(new Sprite());
                shape.getChildAt(1).scaleX = -1;
                shape.x = stage.stageWidth / 2;
                shape.y = stage.stageHeight / 2;
                _shapeHolders.push(shape);
                shape.rotation = 360 / SLICES * n;
                addChild(shape);
            }
        }

        private function createFlakeStage() : void
        {
            for (var n : Number = 0;n < SLICES; n++)
            {
                var slice : Sprite = createSlice(_angle);
                slice.x = stage.stageWidth / 2;
                slice.y = stage.stageHeight / 2;
                slice.rotation = 360 / SLICES * n;
                slice.addEventListener(MouseEvent.MOUSE_DOWN, beginShape);
                slice.alpha = 0;
                _sliceArray.push(slice);
                addChild(slice);
            }
        }
       
        private function createSlice(angle : Number) : Sprite
        {
            var slice : Sprite = new Sprite();
            slice.graphics.moveTo(0, 0);
            slice.graphics.lineStyle(1,0);
            slice.graphics.beginFill(0,.1);
            slice.graphics.lineTo(Math.cos((angle)-Math.PI/2)*RADIUS, Math.sin((angle)-Math.PI/2)*RADIUS);
            slice.graphics.lineTo(Math.cos(-(angle)-Math.PI/2)*RADIUS, Math.sin(-(angle)-Math.PI/2)*RADIUS);
            slice.graphics.lineTo(0, 0);
            slice.graphics.endFill();
           
            slice.graphics.moveTo(0, 0);
            slice.graphics.lineTo(0,Math.sin((_angle)-Math.PI/2)*RADIUS);
            slice.graphics.endFill();
            return slice;
        }
       
        private function beginShape(e : MouseEvent) : void
        {
            _slice = e.target as Sprite;
            for (var i : Number = 0;i < SLICES; i++)
            {
                var side1 : Sprite = _shapeHolders[i].getChildAt(0) as Sprite;
                var side2 : Sprite = _shapeHolders[i].getChildAt(1) as Sprite;
                side1.graphics.lineStyle(1,0xFFFFFF);
                side1.graphics.moveTo(_slice.mouseX, _slice.mouseY);
                side2.graphics.copyFrom(side1.graphics);
            }
           
            stage.addEventListener(MouseEvent.MOUSE_MOVE, renderShape);
            stage.addEventListener(MouseEvent.MOUSE_UP, endShape);
        }
       
        private function renderShape(e : MouseEvent) : void
        {
            for (var i : Number = 0;i < SLICES; i++)
            {
                var side1 : Sprite = _shapeHolders[i].getChildAt(0) as Sprite;
                var side2 : Sprite = _shapeHolders[i].getChildAt(1) as Sprite;
                side1.graphics.lineTo(_slice.mouseX, _slice.mouseY);
                side2.graphics.copyFrom(side1.graphics);
            }
        }

        private function endShape(e : MouseEvent) : void
        {
            stage.removeEventListener(MouseEvent.MOUSE_MOVE, renderShape);
            stage.removeEventListener(MouseEvent.MOUSE_UP, endShape);
        }
    }
}


Leave a Reply