Dynamic text with phong shading using Stok3d

Tuesday, June 9th, 2009

Yesterday David DerSchmale Lenaerts released Stok3d into the wild. When he showed me the demos, I immediately felt (you guessed it) STOKED!

Naturally my first question was : "does it support transparency?!", and with a resounding yes, I just had to try this with text :D

I wanted to generate the texture maps dynamically. It's not quite there yet, but not bad for a quick experiment :)

This took three steps:

  1. Create the diffuse map by generating a text field, taking a bitmap data snapshot of it and adding some bevel to it.
  2. Create the normal map by repeating the previous step, only using Red and Blue colors, with slightly different bevel settings.
  3. Create the specular map by extracting "outlines" using convolution filter.

These three bitmaps look like this:

Here's the code for generating them:

Actionscript:
  1. var font_size:uint=140;
  2. var tf:TextFormat=new TextFormat("Arial",font_size,0xac8ea9,true);
  3. var diffuse:BitmapData=createTextLineBitmapDataFromTextFormat("STOK3D",tf);
  4. diffuse.applyFilter(diffuse,new Rectangle(0,0,diffuse.width,diffuse.height),new Point(),new BevelFilter(5,45,0xFFFFFF,1,0x00,1,10,10,3,3));
  5. _diffuse_map=new Bitmap(diffuse);
  6.  
  7. tf=new TextFormat("Arial",font_size,0xbf6cde,true);
  8. var normal:BitmapData=createTextLineBitmapDataFromTextFormat("STOK3D",tf);
  9. normal.applyFilter(normal,new Rectangle(0,0,normal.width,normal.height),new Point(),new BevelFilter(5,45,0x89a1f9,1,0xFF00FF,1,12,12,3,3));
  10. _normal_map=new Bitmap(normal);
  11.  
  12. tf=new TextFormat("Arial",font_size,0xFFFFFF,true);
  13. var specular:BitmapData=createTextLineBitmapDataFromTextFormat("STOK3D",tf);
  14. specular.applyFilter(specular,new Rectangle(0,0,normal.width,normal.height),new Point(),new BevelFilter(5,45,0x999999,1,0x00,1,0,0));
  15. specular.applyFilter(specular,new Rectangle(0,0,normal.width,normal.height),new Point(),
  16.                                             new ConvolutionFilter(3,3,new Array(0,20,0,20,-80,20,0,20,0),10));
  17. specular.applyFilter(specular,new Rectangle(0,0,normal.width,normal.height),new Point(),new BlurFilter(2,2,2));
  18. _specular_map=new Bitmap(specular);

Then I just grabbed Davids demo code and replaced his textures with mine... and voila:

click image for demo:


storm image from http://www.flickr.com/photos/digitaltool/2569963337/

For some reason the demo doesn't start everytime. Just refresh a few times and you should be STOKED! ;) Also, the specular lights go black sometimes. I'm convinced this is just due to my happy go lucky texture generation, but David claims he needs to look at his code.

check the demo

download the demo as project. This is just my demo file, you'll need to fetch the Stok3d code from google (see link above);

I have to say, Stok3d is pretty damn cool! Sweet work herr schmale :thumbsup: :pirate:

.

Verlet Text Effect

Thursday, June 4th, 2009

Here's a demo I made for my presentation at multi-mania. I played around with verlet integration before and have long wanted to try this technique on text. Once I got the vectorization going, it was just a matter of connecting all the points to "Verlet Sticks".

This turned out to be a little tougher than expected. The tricky thing about Verlet Sticks, is that if you connect the same Verlet Point to multiple Verlet Points, the calculations go berzerk, and your shape quickly vanishes from the canvas into the ether. So the challenge was to find a way to connect all the points of a vector shape, with a minimum number of "individual connections", yet in a way that maintains the integrity of the shape. Keyboard cat played me off multiple times in my attempts :D .

Finally, the solution I landed on, was to create a grid out of all the points. The idea is to create a boundary rectangle, which maintains it's shape via "cross sticks":

Then, create a grid using all the points of your vector shape(s), in such a way that each point connects to a subdivision of the outlines of that "boundary Rectangle":



In the image above you can see the "boundary rectangle" surrounding the vector shape. Then, each point is connected with a vertical and horizontal line. These verticals and horizontals are then connected to a "subdivided" boundary rectangle.



In the demo you can turn "render sticks" on and off to visualize the trick.

Strengths:

  • Can handle pretty much any set of shapes and subshapes

Weaknesses:

  • Due to the "Boundary Rectangle" the shapes "bouncing" is limited, a "T" for instance can't lie diagonally, it always ends up on one of the four sides of the rectangle.
  • The vertical and horizontal lines shouldn't have more than two subdivisions. To fix this, the code first checks for all points along the same x and y, then shifts any instances above 2 by .5 pixels each. It's practically invisible, but it's there.
  • Doesn't support transparency, I guess this could be done with a blendmode or so...
  • More complex shapes can still spazz out and disappear :(

Click to try out the demo here . Ignore the collision physics, it's CRAP! The effect works best with "pixel fonts". The controls are :

  • RENDER : renders the input text with the selected font
  • UPDATE : update one frame of animation (when stopped)
  • PLAY : updates the animation on an enter frame
  • BOUNCE : when the characters are just skidding the floor, spice things up by clicking this button

The code is DIRTY, but does the trick. I've uploaded the source for the brave (or desperate ;) ). It's the same project that I've been using for my last 5 or so blog posts, so there's a lot of redundant code in there. Download it here.

ext steps:

  • compose a clean "all purpose" class for generating such a grid
  • Render shapes with curves, using the drawing api
  • Use nicopteres vectorization code to do this with photos...
  • Improve the curves and optimization, again I think I can use some code from nicopetre

I just wanted to put this out there because, well, it's kind of nifty... More to come, enjoy!

.

Extracting, Analyzing and Optimizing a Vector Shape from a BitmapData Shape

Tuesday, April 28th, 2009

This post has been a long time coming, as I've re-written the code more times than I care to recall. There are just too many ways to skin this damn cat! What I'm sharing now is not a final solution by any means. I just need to stop obsessing for a moment, move on and actually use the code for the cool things I've been planning to do with it! Then obsess further at a more suitable time :D

Also, retrospectively, this post is gonna take some courage and dedication to get through without falling asleep. The code was thrilling to conceive, but less so to write about. Apologies.

Anyway, the challenge is : Given extracted "shape outline points" from a BitmapData shape, I want to optimize these points into the minimum amount which correctly define that shapes perimeter.

Rather than going over the ifs, buts, successes and failiures, here's how the process currently works:

1) Remove redundant points from the "shape outline points"

Since the shape is extracted from a bitmap, the "outline" is composed of rows and columns. Any row or column longer than 2 contains "redudant" points (or pixels), between the start and the end of the line. These are easily removed by looping through the points, keeping track of the current row or column direction.

In the case of the F, the process is done, however, much more work is required for those pesky diagonals and curves...

2) Create "Line" Objects which connect the "non redundant" points

The optimization routines use net.sakri.flash.vector.VectorLine, which has the following properties:

  • start_point:Point
  • end_point:Point
  • type:uint VERTICAL, HORIZONTAL or DIAGONAL
  • direction:Point UP, DOWN, LEFT or RIGHT. Only relevant to VERTICAL and HORIZONTAL lines

The code loops through the "non redundant points", creating Lines. At this point, only VERTICAL and HORIZONTAL lines are present (again, due to the "rows and columns" nature of bitmaps). The loop sets each "line direction", based on the lines start and end positions. See the little red arrows below:

3) Use the directions of the "Line" Objects to discover "break points" or "turning points"

Essentially the goal is to isolate Lines, Diagonals and Curves. I have tried approaches ad infinitum, and so far the following delivers the most Bang For my Buck:

  • Loop through the lines, minding a "vertical anchor line" and a "horizontal anchor line".
  • Whenever the direction of lines changes from the current anchors, this can be seen as a "break" or a "turning point".
  • Store these "break points" in a list.
  • Use the "break point" as the current anchor, and continue looping.

The image below is a "single clockwise pass", the "turning points" are represented as blue pixels accentuated by totally awesome green circles.

Following the lines starting from the top left corner, you should "discover" the same "break points".

The first "horizontal direction change" takes place at the bottom right corner of the M. Up until this point all the horizontal lines go "RIGHT".

The first "vertical direction change" occurs at the bottom of the first "V" shape between the M's legs. Up until this point, all the verticals point DOWN, and after the turn point UP.

This first "pass" uncovers 10 break points, which could be used to analyze the "sub components" (deal with diagonals and curves). However, it's painfully clear that more break points are needed. In the "M", there are 13 which define the shape. So, a "second" pass sounds like a good idea, this time counterclockwise (or in reverse order). This results in different "break points" than the clockwise pass, (again just follow the lines with your eyes...).

Below is a "single counter-clockwise pass", the "turning points" are again visualized as blue pixels surrounded by gratuitous neon green pixels:

Putting these two together, running clockwise and counter-clockwise gives us:

Now we've got 16 break points for "M"! This is good enough, the main lines and diagonals are isolated. There was much rejoicement!

Here's the same for a character with some curves:

Nifty as this might sound, the approach isn't without it's shortcomings. "Staircases" and "Curve Staircases" are a thorn in my side. In such cases, there are "clear" changes in visual direction, yet, neither the HORIZONTAL nor the VERTICAL direction changes in a "staircase". Experience the phenomena in characters including T,F,4,S,a, etc. etc. Witness the F below:

At this point, we say "F it", and move on.

4) Use the "break points" to cut the shape up into segments, and analyze

Loop again. This time looking at the sets of VectorLines separated by "break points":

  • Any set of 1 must be a Vertical or a Horizontal line.
  • Detect Diagonals
  • Handle Curves and "complex shapes"

Here's my "CPU Cheap" approach to detecting a Diagonal in a set:

Actionscript:
  1. protected var _diagonal_buffer:Number=.15;
  2.         protected function isDiagonal(lines_vect:Vector.<VectorLine>,index1:uint,index2:uint):Boolean{
  3.             var first:Point=VectorLine(lines_vect[index1]).start_point;
  4.             var middle:Point=getMiddlePointInVLineSegment(lines_vect,index1,index2);
  5.             var last:Point=VectorLine(lines_vect[index2]).start_point;
  6.             var diff:Number=Point.distance(first,last)-Point.distance(first,middle)-Point.distance(middle,last);
  7.             return Math.abs(diff)<_diagonal_buffer;
  8.         }

The code grabs the "middle point" within a set of Lines. Then it compares the distance from the "sets start point to finish point", with the added distance of "start TO midpoint+midpoint TO endpoint". If the difference is less than "_diagonal_buffer", it's a diagonal. Accept it.

The remaining shapes fall under the categories of "Curves" and "Complex Shapes". Both can actually be treated the same : Based on a parametrized "Curve Accuracy", the remaining shapes can just be divided into "sub segments".

Let's say a Curve has 200 pixels, which might translate to 50 "Lines" once "redundant points" are removed. If "Curve Accuracy" is set to 15, it's relatively easy to loop through the 50 lines, and isolate them into "Sub Lines" grouped by this "Curve Accuracy". Maybe an easier way to picture this is just to chop up a curve by choosing every X points along it's path. If that makes no sense, just check out the code.

There's a few more optimizations, but I can't be arsed to go into it right now. This post is long enough as it is.


Click here for the demo (Comic Sans pictured below):

Download the Source (Requires FlexBuilder, with sdk 3.2+)

Congrats, give yourself a hearty pat on the back for making it all the way through! You have our Gratitude! I personally guarantee future posts with better entertainment value using this code base and it's oncoming descendants :D

My entry to Bit-101s 25 Lines competition

Wednesday, November 19th, 2008

I participated in the original one back at the were-here forums, and couldn't resist giving it a shot this time around as well :) If you don't know, this is what I'm talking about.

In the spirit of my upcoming FITC Talk, I naturally went for a text effect! Click the image to see it in action :



I started with a good 50 lines of code... I'm just getting my head around the tricks involved with using Graphics.drawTriangles(). Took a bit of effort to a) get it to work, b) get it down to 25 lines. Basically the code:

  • Creates a textfield
  • Grabs a bitmapData of it, this is the "material"
  • Creates a cylinder with a loop (vertices, triangles and uv data)
  • In an enterframe it:
    • Rotates a matrix in y and z
    • Transforms that rotation to the "mesh" and it to the perspective projection
    • Draws the triangles using the material as a bitmap fill

At the end, I got the line count down to the point where I could even add some glow and z rotation :ooh: The screenshot looks a bit "pixely", and it is, because the font is not embedded.

The 25lines website proclaims : "You probably don't want to share your code prematurely, but that's up to you", well.... Last time I was probably 60th out of 61 entries so I doubt this is gonna kill my chances :D I'll never match up to the math gurus out there, but hey, mine has a joke in it!

The code:

Actionscript:
  1. // 3 free lines! Alter the parameters of the following lines or remove them.
  2. // Do not substitute other code for the three lines in this section
  3. [SWF(width=800, height=380, backgroundColor=0x000044, frameRate=31)]
  4. stage.align = StageAlign.TOP_LEFT;
  5. stage.scaleMode = StageScaleMode.NO_SCALE;
  6. // 25 lines begins here!
  7.  
  8. var tf:TextField=new TextField();
  9. tf.defaultTextFormat=new TextFormat("Helvetica",120,0xFFFFFF,true);//Hopefully all machines have Helvetica...
  10. tf.text="Circular Logic Works Because ";
  11. tf.autoSize=TextFieldAutoSize.LEFT;
  12. tf.filters=[new GlowFilter(0x2222BB,.7,12,12,4,3)]
  13. var material:BitmapData=new BitmapData(tf.width+10,tf.height+10,false,0x000044);
  14. material.draw(tf,null,null,null,null,true);
  15. var vertices:Vector.<Number>=new Vector.<Number>(),t_vertices:Vector.<Number>=new Vector.<Number>(),points:Vector.<Number>=new Vector.<Number>(),triangles:Vector.<int>=new Vector.<int>(),uv:Vector.<Number>=new Vector.<Number>(),angle:Number=0,radius:Number=material.width/Math.PI/2,slices:uint=20,x_rot:Number=0,x_rot_speed:Number=1,y_rot:Number=-150,z_rot:Number=0,z_rot_dir:Number=-.1;
  16. for(var i:int=0;i<=slices;i++){
  17.     vertices.push(radius*Math.cos(angle*(Math.PI/180)),0,radius*Math.sin(angle*(Math.PI/180)),radius*Math.cos(angle*(Math.PI/180)),material.height,radius*Math.sin(angle*(Math.PI/180)));
  18.     uv.push(1-angle/360,0,0,1-angle/360,1,0);
  19.     angle+=(360/slices);
  20.     if(i>1)triangles.push(i*2-4, i*2-2, i*2-3,i*2-3, i*2-2, i*2-1);
  21. }
  22. triangles.push(i*2-4, i*2-2, i*2-3,i*2-3, i*2-2, i*2-1);
  23. addEventListener("enterFrame",function handleEnterFrame(e:Event=null) {
  24.     var transform_matrix:Matrix3D=new Matrix3D();
  25.     transform_matrix.prependTranslation(stage.stageWidth/2,stage.stageHeight/2-material.height/2,0);//maybe better to use a container?
  26.     transform_matrix.prependRotation(y_rot--, Vector3D.Y_AXIS);
  27.     transform_matrix.prependRotation((z_rot+=z_rot_dir), Vector3D.Z_AXIS);
  28.     if(z_rot>20 || z_rot<-20)z_rot_dir*=-1;
  29.     transform_matrix.transformVectors(vertices, t_vertices);
  30.     Utils3D.projectVectors(new Matrix3D(), t_vertices, points, uv);
  31.     graphics.clear();
  32.     graphics.beginBitmapFill(material);
  33.     graphics.drawTriangles(points, triangles, uv, TriangleCulling.POSITIVE);
  34. });
  35. // 25 lines ends here!

The source in an easier "cut and paste" format : circular_logic.txt

Again, the html page with the compiled swf in it : 25Lines.html

anyway, fingers crossed! Looking forwards to seeing the other entries :)

Star Wars intro text effect with fp10 in 14 lines of code

Wednesday, November 12th, 2008

I finally installed flash CS4, and the first thing I had to do was recreate "the intro". This is practically guaranteed to make you cry 25 years from now! I once again I proclaim my absolute joy of having the 3d features in as3 :D


If you see nada, it's not because I'm lying about this superhuman feat of engineering ;) but because you don't gots teh flash player 10. It's interesting to see the flicker once you let the sucker scroll for a while... I guess some mask or so might sort that out...

Actionscript:
  1. var rotated_holder:Sprite=new Sprite();
  2. rotated_holder.y=360;
  3. rotated_holder.rotationX=-70;
  4. var text_field:TextField=new TextField();
  5. text_field.width=480;
  6. text_field.multiline=true;
  7. text_field.wordWrap=true;
  8. text_field.autoSize=TextFieldAutoSize.LEFT;
  9. var text_format:TextFormat=new TextFormat("Arial",42,0xE3EF7D,true,null,null,null,null,TextFormatAlign.JUSTIFY);
  10. text_field.text="It is a period of civil war. Rebel [REST OF TEXT HERE]....";
  11. text_field.setTextFormat(text_format);
  12. rotated_holder.addChild(text_field);
  13. addChild(rotated_holder);
  14. addEventListener(Event.ENTER_FRAME,function(e:Event):void{text_field.y-=1;});

nifty no? About 6 lines into this though, I realized it could be even quicker done just by plain old motion tweening :D (which I incidentally also find cool, you know, that that's possible).