The following was originally posted on the StruckAxiom blog on 2/2/10.

[vimeo 9166910]

Today’s Site of the Day [editor's note: 2/2/10] on FWA is none other than the recently unleashed LEGOCLICK.com, designed and developed by the dudes and lady-dudes of Struck/Axiom in association with Pereira & O’Dell. (If you haven’t seen it yet, you should check it out.) To mark the occasion, we’d like to share a technical peak behind the curtain of one of the more popular features of the site – removing a block of content.

A word about perspective:

Early on, we considered different possibilities for how the LEGO bricks and the environment could be achieved technically. We thought about using one of the Flash 3D engines, and discussed solutions as exotic as using a server-side AIR app to render our objects as FLVs, but ultimately decided that we wouldn’t be able to achieve the performance or the visual polish that we demand of ourselves.

So, in a move that would send my college drawing professor into hysterics, we decided that visual polish and a stylized sense of depth were more important than “correct perspective.” That’s why everything has a bit of perspective to suggest depth, but the visuals don’t have consistent vanishing points, nor do they shift in perspective as they move relative to the viewer.

A word about the feature:

Removing a block of content is hardly an integral part of the site. However, it represented something very vital to both the LEGO brand and the site – fun. After all, who among us never built something only to smash it apart? (Or at very least, had a beloved creation subjected to the wrath of a brother or sister?)

Phase 1: The bricks

Creation and layout

When a block wall is broken apart, we’re switching from one large graphic of the block wall to many individual images, which each represent a brick. The correct number of bricks is created and positioned to fill in the block wall. (We decided to leave out the 1×2s that fill the edges on alternating rows – in the end, they won’t be missed in the flurry of movement.) In keeping with our intentional disregard for correct perspect
ive, each brick has the same beginning perspective.

block_layout

Movin’ them bricks

To make the bricks move, we need to give them some velocity. We start with a startingVelocity, which is random (within a range), and split it between x, y, and z – the amount of the total velocity that z gets is determined by its distance from the center of the block wall, and the remainder is split between x and y, according to the angle the block is from the center. While _xVel and _yVel will move the block in x and y, _zVel will affect the block’s pseudoZ property, which will affect its scale, making it appear to move outwards.

velocity_math<mce:script type=

var zRatio:Number = (maxDistance - distance)/maxDistance;
_zVel = zRatio * startingVelocity;
_xVel = Math.cos(angle) * (startingVelocity - _zVel);
_yVel = Math.sin(angle) * (startingVelocity - _zVel);

Each block is listening for ENTER_FRAME events. Every frame, each block calls the update() function, which moves the block according to its velocities, and then adds a gravity constant onto the y velocity. The update() function is separated from the event handler so that it can be called independently – when the explosion is started, the blocks nearest the center call update() twice automatically, and the blocks slightly further out call it once, to give the illusion of being blown apart from the center. When the block is sufficiently offstage, the event listener is removed and an event is dispatched to have the block’s parent remove it.

 

private function enterFrameHandler(e:Event):void
{
     update();
     if(y > stage.stageHeight*1.5)
     {
          removeEventListener(Event.ENTER_FRAME,enterFrameHandler);
          dispatchEvent(new ExplodeBrickEvent(
ExplodeBrickEvent.EXPLODE_BRICK_OFFSCREEN));
     }
}


private function update():void
{
     x += _xVel;
     y += _yVel;
     pseudoZ += _zVel;
     _yVel += GRAVITY;
}

public function set pseudoZ(value:Number):void
{
     _z = value;
     _asset.scaleX = _asset.scaleY = _scaleAdjust * value/7500;
}
public function get pseudoZ():Number
{
     return _z;
}

 

Give it a spin

Now that the pieces are moving correctly, it’s time to make them appear to spin. To achieve this effect, we went back into Cinema4D and created 5 different sequences of our 3d block spinning in various directions. Each block is randomly assigned one of these sequences to play through as it falls.

We noticed that in all of the chaos of the initial explosion, the eye missed most of the spinning. In order to save on file size / load time (since every additional frame is an additional png, meaning X kilobytes and Z load time), we changed the timeline of our spin sequences so that the sequence waited for the initial flurry of motion to complete before resuming its spin.

Phase 2: Putting content on the bricks

Capturing and chopping content

Using AS3’s BitmapData functionality, we’re able to take the state of objects and draw them into a bitmap. To get the content we want, we’re turning off the block background (switching visibility to false), capturing the bitmap data (BitmapData.draw()), and then turning the block background back on (until the wall is ready to explode of course – see Phase 1: Creation and Layout). Actually, we’re capturing a bunch of small bitmap datas – one for each block that we’re creating, capturing only the content that should go on that block. Then we send it on to the block that we’re creating for the beginning of the explosion.

var bitmapData:BitmapData = new BitmapData(BLOCK_WIDTH,
BLOCK_HEIGHT,true,0×000000);

var matrix:Matrix = new Matrix();

matrix.translate( -DisplayObject(_blockArray[i]).x,
-DisplayObject(_blockArray[i]).y );

bitmapData.draw(Sprite(blockWall),matrix);

IExplodeBlock(_blockArray[i]).setFace(bitmapData);

Distorting the content to fit the face

As the blocks spin and tumble out of frame, we want the content on the face of them to move with the block. Since we’ve captured that bitmap data, we can actually use the DistortImage class, developed by the evil geniuses at Sandy3D.

DistortImage takes bitmapData and distorts it using corner pinning – you tell each corner where it should be positioned, and the image is stretched, squashed and distorted to fit those corners.

So how do we know where the corners are? Some good old-fashioned Flash production work. In our Flash MovieClip, we have four movie clips, one for each corner, that are invisible (they are semi-transparent here, for demonstration purposes). On every frame, we position them on each appropriate corner of the graphic.

block_assets

So, when we explode the wall into blocks, on every frame we are distorting the image to fit the corners, as they’re positioned on that frame.

Which brings us to a special consideration – some of the spin sequences result in the front face rotating away so that it’s unseen – what do we do in those cases? We simply place all for corners in the exact same location – like 0,0 – and check for it in our code. If the corners are positioned identically, we just had the graphics that we’re mapping to the face.

break_apart

public function setFace(bitmapData:BitmapData):void
{
_asset.scaleX = _asset.scaleY = _scaleAdjust;
_faceDistortion = new DistortImage( bitmapData.width, bitmapData.height, 3, 3);
_faceBitmap = new Bitmap( bitmapData, PixelSnapping.NEVER, false);
addChild(_faceShape);
_faceDistortion.setTransform( _faceShape.graphics,
_faceBitmap.bitmapData, getCornerTL(), getCornerTR(), getCornerBR(), getCornerBL());
}


private function update():void
{
if(getCornerBL().x != getCornerTR().x || getCornerBL().y != getCornerTR().y)
{
_faceShape.graphics.clear();
_faceDistortion.setTransform( _faceShape.graphics,
_faceBitmap.bitmapData, getCornerTL(), getCornerTR(), getCornerBR(), getCornerBL());
}
else
{
_faceShape.visible = false;
}

x += _xVel;
y += _yVel;
pseudoZ += _zVel;
_yVel += GRAVITY;
}

public function getCornerTR():Point
{
return new Point( _asset.cornerTR.x*_asset.scaleX,
_asset.cornerTR.y*_asset.scaleY );
}

public function getCornerTL():Point
{
return new Point( _asset.cornerTL.x*_asset.scaleX,
_asset.cornerTL.y*_asset.scaleY );
}

public function getCornerBR():Point
{
return new Point( _asset.cornerBR.x*_asset.scaleX,
_asset.cornerBR.y*_asset.scaleY );
}

public function getCornerBL():Point
{
return new Point( _asset.cornerBL.x*_asset.scaleX,
_asset.cornerBL.y*_asset.scaleY );
}

Processing the distortion

This was all well and good, except for one small thing – it was totally overtaxing the processor, chugging like mad. It quickly became clear that it wouldn’t look smooth on even the best of computers. We did not give up hope, however – we figured that we could find a point to pause for a moment, run each frame of distortion that we would need, and save each as a bitmap. That way, instead of manipulating a bitmap every frame, with all of the smushing and interpolating of p
ixels that required, we could just swap a bitmap in and out.

To do this, we needed to capture those corner positions from the MovieClip. As soon as the movie clip assets are loaded in – we’re loading those separately, by the way, so that the site can get up and running without all those extra images to loa
d – we run a timer, on each timer event advancing the MovieClip a frame and recording corner positions for each sequence into static arrays.

Now that we have all that information stored, we can go through the whole process again – splitting the wall into blocks, sending it a chunk of BitmapData, and assigning it a sequence. We use a timer to progress through all the different frames, using the corner information stored in the static arrays to distort the BitmapData and then save it as a new bitmap into an array.

Once every block is done distorting and storing its sequence of bitmaps, the explosion occurs – the blocks move outward and begin to spin, and the bitmaps are swapped out to show the correct distortion for each frame.

Phase 3: The minifigure

A word about the minifig

The minifig is an instantly recognizable part of the LEGO brand, so what better way to inject some more fun into the site than having a minifig with a jetpack flying around? Better yet, why not have the minifig be the impetus behind breaking the wall apart? The programming isn’t super complex, but we’ll cover it quickly here.

Jetting around

When a block is selected, the minifig is positioned offstage – using a bit of random logic (if (Math.random() > .5)) to determine which side of the screen to place him on. Another bit of random logic determines whether the minifig will fly directly behind the block, or whether it will fly around the front of the blocks first. The motion is handled by Tweener, using bezier points to create smooth curves. If the minifig is flying directly, he is immediately swapped from a container sprite that sits above the block walls to one that sits below them – if flying around, he gets swapped at the midpoint of his flight.

Do the twist

The minifig wouldn’t look that great if he was just facing straight ahead all the time, so the team created a sequence of renders of him rotating side-to-side. Armed with these frames, we built an enterframe handler into the minifigure class. Every frame, we compared the current x position to the x position in the previous frame. Based on that difference, the minifig is rotated, and the image sequence goes to the appropriate position.

twist_miniman

var targetRotation:Number;
if((x - _previous.x) < 30 && (x - _previous.x) > -30 )
{
targetRotation = (x - _previous.x);
}
else if ((x - _previous.x) >= 30)
{
targetRotation = 30;
}
else
{
targetRotation = -30;
}
_asset.rotation = (targetRotation - _asset.rotation)*.25
+ _asset.rotation;


if(_twist)
{
var targetFrame:int = Math.round(13 - _asset.rotation/2.5);
targetFrame = Math.min(targetFrame, 24);
targetFrame = Math.max(targetFrame, 1);
_asset.gotoAndStop(targetFrame);
}

_previous.x = x;
_previous.y = y;

Break on through

Once the minifig is behind the block wall, we run the pre-processing for the explosion, rendering out all of the distorted bitmaps for the explosion. As soon as all the pieces are ready, the minifig plays through one of three “break through” sequences – randomly chosen – and the block explosion goes off. Once all the blocks are offscreen, the minifig gets sent back offscreen using Tweener.

break_through_minifig

And that’s it!

In a nutshell, that’s how our team pulled off the explosion effect – fully realized through a combination of 3d work, Flash production, and AS3. Hope it was educational – now go play with the site some more!