3d Text by Extruding BitmapData Snapshots of Text

Tuesday, May 5th, 2009

Putting together a number of previous blog posts, here’s the current state of my 3d text. In terms of speed and quality, it’s a big improvement on the examples I was showing at FITC in Amsterdam, old example here.

So, the process is (links to blog posts with explanations and source code):

Here’s some screen shots, click images for the demo :

The colors are random, use the widgets to choose a font, the text size and depth of the extrude.

On my machine the framerate is pretty steady in the 20’s even with longer strings. The text box on the right displays the duration of the entire process described above.

Check out the demo

Download the source (I know, the project has the same name as the last 3, because it is, I’ve just added a bunch more code to it). Parts of it I’m quite happy with, others less so, but you know, there’s always room for improvement ;)

Mouse interactions on Text Layout Framework InlineGraphicElements in EditMode

Thursday, March 26th, 2009

One slightly frustrating (but totally logical) feature of using editmode in TLF is that inserted InlineGraphicElements lose their "interactivity". Instead, they are treated the same as text characters, that is, the user can select them as if they were selecting text:

There was a comment In my previous entry about the possibility of mouse interaction with inserted graphics.

I have a way to do this, a bit hackish, but it does the job. Every added graphic needs to be stored in an array. When a user clicks, the code loops through all the graphics and checks if one of them happens to be under the mouse. This is achieved using the trusty old localToGlobal() method:

Actionscript:
  1. protected function handleMouseDown(e:MouseEvent):void{
  2.     var hitrect:Rectangle;
  3.     var bm:Bitmap;
  4.     for(var i:uint=0;i<_images.length;i++){
  5.         bm=Bitmap(_images[i]);
  6.         var ltg:Point=bm.localToGlobal(new Point(0,0));
  7.         hitrect=new Rectangle(ltg.x,ltg.y,bm.width,bm.height);
  8.         if(hitrect.contains(mouseX,mouseY)){
  9.             mx.controls.Alert.show("so, I see you have clicked on an image eh?");
  10.             break;
  11.         }
  12.     }
  13. }

This is very useful if you want to edit the graphic somehow, for instance, in the RTE that I'm currently working on I use this for "table support". In the TextFlow, my tables are represented as bitmaps, when clicked, the actual DataGrid is opened and ready to edit. When finished, a new BitmapData is grabbed and the InlineGraphic in the TextFlow is updated.

I have found one "bug" (or a feature?!), if an image is taller than (somewhere around 1000 pixels), the image is no longer properly "wrapped", and actually starts to cover some text.

It's also possible to listen to MouseMove, and set some rollover/out states to the graphics. You can see a demo here and download the source mxml file here.

Simple Shape Extruder using FP10 Drawing API

Tuesday, March 10th, 2009

My SoTexty 3d Text Effect demo uses a Class I wrote a which takes a polygon in the form of a Vector of Points, and extrudes it into a 3dMesh of sorts.

Click the image for a demo:

The colors are random, as is the outline of the shapes... Refresh if the result doesn't please your aesthetics ;)

Here's the snippet that uses the class:

Actionscript:
  1. protected function testExtrude():void{
  2.     var points:Vector.<Point>=createCircularPath(new Point(300,300),200,250,50);
  3.     var negative_shape_points:Vector.<Point>=createCircularPath(new Point(300,300),90,190,40);
  4.     var points2:Vector.<Point>=createCircularPath(new Point(550,550),80,20,20);
  5.     var points3:Vector.<Point>=createCircularPath(new Point(300,650),80,20,20);
  6.     var points4:Vector.<Point>=createCircularPath(new Point(650,300),80,20,20);
  7.     _extruded=SimpleShapeExtruderDrawAPI.createExtrudedShapeFromPoints(100,
  8.                                                                 Vector.<Vector.<Point>>([points,points2,points3,points4]),
  9.                                                                 Vector.<Vector.<Point>>([negative_shape_points]),
  10.                                                                 Math.random()*0xFFFFFF,0xFF000000+Math.random()*0xFFFFFF
  11.                                                                 );
  12.     _extruded.adjustCenter(300,300);
  13.     _extruded.x=300;
  14.     _extruded.y=300;
  15.     addChild(_extruded);
  16.     addEventListener(Event.ENTER_FRAME,rotateExtruded);
  17. }

Instead of Triangulating the shape, I used a "shortcut" which I learned a while back from Den Ivanov. The Mesh contains a "front" and a "back" which are a triangle pair, who use the "original" shape as their material:

The large red square with the diagonal represents the "front", the "back" is identical. A curiosity about this screenshot is that the "filled triangles" are z-sorted correctly, however, there are some issues with the outlines. Meh.

The code accepts a Vector of Positive and Negative "shapes", which are rendered into a BitmapData and used as a "Material" by the 3DMesh. Here is a screenshot of what the "rendered" material looks like:

The filled box on the right is the "material" used by the extruded sides.

Notice the white space surrounding the shape. An annoying issue I ran into was that the "front" and the "back" had unwanted artifacts when using a "1 to 1" ratio "material". The UV mapping would go from 0 to 50% horizontally, and 0 to 100% vertically. For some reason, the edges (when rotated) would render an arbitrary "line" on any of the four sides.

The solution was to add white space, and grab the material using "less precise" percentages, which unfortunately results in some "spaces" on the edges of the "shape" material and the extruded side triangles. Another Meh.

It's not perfect, but I figured the code might be fun to play with...

Download the flex project here

(requires Flex SDK 3.2 or higher)

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).