Hexagonal Animation Effect
This is going to be a short post. It is the progress I made in converting some code that I found at wonderfl. The link to the demo where I got the original source from, is in the code below. I have also included a link to the image I am using. Use the arrow keys to control the effect.
Essentially I took the algorithm and tried to make some sense of it. The other agenda was to provide a generic method which would apply the effect to any display object passed to it. Seeing as I spent most of the time trying to figure out the code and have not refactored it yet, it is still quite a bit messy and poorly designed. Lastly, minor pre-calculation performance enhancements were made to the algorithm.
The algorithm is relatively simple.
- It starts at the center point provided and then chooses neighbours at random.
- If there is a neighbour at the location then it will traverse to that neighbour and repeat.
- If there is no neighbour, it is then assumed it is at the edge of the structure and thus creates a new hexagon
- It then links it accordingly to keep the animation fluid
- The animation speed is currently tied to the fps of the flash object. There is a plan to allow for variable speed control, using a timer, when the class is refactored.
- There are two drawing constructions
- One for the mask (plain hexagons)
- Another for the hexagonal border (currently attached to the container provided). An enhancement would be to [attach / remove] the border [to / from] the parent instead.
Code
With the enhancements suggested above, the stage will not have to be passed in. It is currently used only to tie the animation to the frame-rate. Secondly the container will not have to be passed in, as the parent can be used instead of the container.
package avdw.generate.effect {
import avdw.generate.effect.shape.Hexagon;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.geom.Point;
/**
* TODO: add in a speed parameter to control the animation speed
*
* This class will apply an animated mask to an image.
* The animation is a collection of hexagons being built around a point.
* Where the hexagons are built the mask is applied to reveal the image.
*
* Reference: http://wonderfl.net/c/jLM1
* ...
* @author Andrew van der Westhuizen
*/
public class HexagonalMask {
private const angles:Vector.<Number> = new Vector.<Number>();
private var stage:Stage;
private var container:Sprite;
private var displayObject:DisplayObject;
private var hexagons:int;
private var diameter:int;
private var firstHex:Hexagon;
private const V:Number = 30;
private var sprite:Sprite = new Sprite(); // hexigons
private var shape:Shape = new Shape(); // hexigon borders
public function HexagonalMask(stage:Stage, container:Sprite, displayObject:DisplayObject):void {
this.stage = stage;
this.container = container;
this.displayObject = displayObject;
}
public function remove():void {
if (container.contains(sprite))
container.removeChild(sprite);
if (container.contains(shape))
container.removeChild(shape);
displayObject.mask = null;
}
/**
*
* @param start
* @param hexagons
* @param diameter
*/
public function apply(start:Point, hexagons:int = 300, diameter:int = 20):void {
var count:int;
this.diameter = diameter;
this.hexagons = hexagons;
displayObject.mask = sprite;
container.addChild(sprite);
container.addChild(shape);
for (count = 0; count < 6; count++) {
angles.push(Math.PI / 180 * (60 * count));
}
firstHex = new Hexagon();
firstHex.origin = start;
firstHex.v = 0;
var currHex:Hexagon = firstHex;
for (count = 0; count < hexagons; count++) {
currHex.next = new Hexagon();
var tmpHex:Hexagon = firstHex;
// traverse from origin to outside hex randomly
// create new hex on border and link it as next for animation
while (true) {
var idx:uint = Math.floor(Math.random() * 6); // choose random neighbour
if (!tmpHex.link[idx]) { // if it does not exist
// create it and link it
currHex.next.origin.x = tmpHex.origin.x + diameter * Math.cos(angles[idx]);
currHex.next.origin.y = tmpHex.origin.y + diameter * Math.sin(angles[idx]);
checkLink(currHex.next);
break; // and break, thus will have correct # hexagons
} else { // if it exists
tmpHex = tmpHex.link[idx]; // traverse it
}
}
currHex.next.v = Math.random() * -20 + (-10 * count);
currHex = currHex.next;
}
stage.addEventListener(Event.ENTER_FRAME, animate);
}
/**
* This method assigns a new link on the correct hexigon.
*
* @param cp
*/
private function checkLink(cp:Hexagon):void {
for (var i:uint = 0; i < 6; i++) {
var cx:Number = cp.origin.x + diameter * Math.cos(angles[i]);
var cy:Number = cp.origin.y + diameter * Math.sin(angles[i]);
var p:Hexagon = firstHex;
while (p.next) {
if (cx < p.origin.x + 2 && cx > p.origin.x - 2 && cy < p.origin.y + 2 && cy > p.origin.y - 2) {
cp.link[i] = p;
p.link[(i + 3) % 6] = cp;
}
p = p.next;
}
}
}
private function animate(e:Event = null):void {
var g:Graphics = shape.graphics; // border graphics
var sg:Graphics = sprite.graphics; // hexigon graphics
var p:Hexagon = firstHex;
var cnt:uint = 0;
sg.clear();
g.clear();
g.lineStyle(1, 0x000000);
while (p) {
if (p.v < 610)
p.v += V;
if (p.v >= 0) {
if (p.v >= 600) {
g.beginFill(0xFFFFFF, p.a / 100 - (Math.random()));
if (p.a > 0)
p.a -= 5;
sg.beginFill(0xAAFFAA, 0.1);
}
var bx:Number = 0;
var by:Number = 0;
for (var i:Number = 0; i <= 6; i++) {
var ang:Number = Math.PI / 180 * (60 * i - 90);
var lx:Number = p.origin.x + diameter / 2 * Math.cos(ang);
var ly:Number = p.origin.y + diameter / 2 * Math.sin(ang);
if (i == 0) {
g.moveTo(lx, ly);
sg.moveTo(lx, ly);
bx = lx;
by = ly;
} else {
if (i * 100 <= p.v) {
g.lineTo(lx, ly);
if (p.v >= 600)
sg.lineTo(lx, ly);
bx = lx;
by = ly;
}
if (i * 100 > p.v && (i - 1) * 100 <= p.v) {
g.lineTo(bx + (lx - bx) * (p.v % 100) / 100, by + (ly - by) * (p.v % 100) / 100);
}
}
}
g.endFill();
}
p = p.next;
cnt++;
}
}
}
}