Skip to content

Instantly share code, notes, and snippets.

@leeprobert
Created April 23, 2012 14:40
Show Gist options
  • Save leeprobert/2471336 to your computer and use it in GitHub Desktop.
Save leeprobert/2471336 to your computer and use it in GitHub Desktop.
A class for drawing a block of text and then drawing an extrusion path from the bounds of the rectangle
package com.cc.math
{
/**
* Full details: http://gamedev.michaeljameswilliams.com/2009/04/24/angles-in-flash/
* Hungarian notation idea: http://www.joelonsoftware.com/articles/Wrong.html
* Optimisation credit: http://www.nbilyk.com/optimizing-actionscript-3
*
* Usage: import com.michaeljameswilliams.gamedev.angles.Angle
* var degAngle:Number = Angle.degFromRad( radAngle );
*
* @author MichaelJWilliams
*/
public class Angle
{
private static const localPi:Number = Math.PI;
private static const localTwoPi:Number = Math.PI * 2;
private static const oneOver180:Number = 1 / 180;
private static const oneOverPi:Number = 1 / localPi;
public function Angle()
{
trace( "The com.michaeljameswilliams.gamedev.angles.Angle class does not need to be instantiated" );
}
/**
* @param p_degInput Angle, in degrees
* @return Angle, in degrees, in range 0 to 360
*/
public static function degFromDeg( p_degInput:Number ):Number
{
var degOutput:Number = p_degInput;
while ( degOutput >= 360 )
{
degOutput -= 360;
}
while ( degOutput < 0 )
{
degOutput += 360;
}
return degOutput;
}
/**
* @param p_rotInput Angle, in degrees
* @return Angle, in degrees, in range -180 to + 180
*/
public static function rotFromRot( p_rotInput:Number ):Number
{
var rotOutput:Number = p_rotInput;
while ( rotOutput > 180 )
{
rotOutput -= 360;
}
while ( rotOutput < -180 )
{
rotOutput += 360;
}
return rotOutput;
}
/**
* @param p_radInput Angle, in radians
* @return Angle, in radians, in range 0 to ( 2 * pi )
*/
public static function radFromRad( p_radInput:Number ):Number
{
var radOutput:Number = p_radInput;
while ( radOutput >= localTwoPi )
{
radOutput -= localTwoPi;
}
while ( radOutput < -localTwoPi )
{
radOutput += localTwoPi;
}
return radOutput;
}
/**
* @param p_degInput Angle, in degrees
* @return Angle, in degrees, in range -180 to +180
*/
public static function rotFromDeg( p_degInput:Number ):Number
{
var rotOutput:Number = p_degInput;
while ( rotOutput > 180 )
{
rotOutput -= 360;
}
while ( rotOutput < -180 )
{
rotOutput += 360;
}
return rotOutput;
}
/**
* @param p_rotInput Angle, in degrees
* @return Angle, in degrees, in range 0 to 360
*/
public static function degFromRot( p_rotInput:Number ):Number
{
var degOutput:Number = p_rotInput;
while ( degOutput >= 360 )
{
degOutput -= 360;
}
while ( degOutput < 0 )
{
degOutput += 360;
}
return degOutput;
}
/**
* @param p_radInput Angle, in radians
* @return Angle, in degrees, in range 0 to 360
*/
public static function degFromRad( p_radInput:Number ):Number
{
var degOutput:Number = 180 * oneOverPi * radFromRad( p_radInput );
return degOutput;
}
/**
* @param p_degInput Angle, in degrees
* @return Angle, in radians, in range 0 to ( 2 * pi )
*/
public static function radFromDeg( p_degInput:Number ):Number
{
var radOutput:Number = localPi * oneOver180 * degFromDeg( p_degInput );
return radOutput;
}
/**
* @param p_radInput Angle, in radians
* @return Angle, in degrees, in range -180 to +180
*/
public static function rotFromRad( p_radInput:Number ):Number
{
return rotFromDeg( degFromRad( p_radInput ) );
}
/**
* @param p_rotInput Angle, in degrees
* @return Angle, in radians, in range 0 to ( 2 * pi )
*/
public static function radFromRot( p_rotInput:Number ):Number
{
return radFromDeg( degFromRot( p_rotInput ) );
}
}
}
package com.cc.math
{
/**
* Full details: http://gamedev.michaeljameswilliams.com/2009/04/24/angles-in-flash/
* Hungarian notation idea: http://www.joelonsoftware.com/articles/Wrong.html
* Optimisation credit: http://www.nbilyk.com/optimizing-actionscript-3
*
* Use this instead of the Angle functions when you need extra speed
* and are going to use the functions multiple times (e.g. over a loop)
*
* Usage: import com.michaeljameswilliams.gamedev.angles.Angle
* var angleUtil:AngleUtil = new AngleUtil();
* var degAngle:Number = angleUtil.degFromRad( radAngle );
*
* @author MichaelJWilliams
*/
public class AngleUtil
{
private const localPi:Number = Math.PI;
private const localTwoPi:Number = Math.PI * 2;
private const oneOver180:Number = 1 / 180;
private const oneOverPi:Number = 1 / localPi;
public function AngleUtil()
{
}
/**
* @param p_degInput Angle, in degrees
* @return Angle, in degrees, in range 0 to 360
*/
public function degFromDeg( p_degInput:Number ):Number
{
var degOutput:Number = p_degInput;
while ( degOutput >= 360 )
{
degOutput -= 360;
}
while ( degOutput < 0 )
{
degOutput += 360;
}
return degOutput;
}
/**
* @param p_rotInput Angle, in degrees
* @return Angle, in degrees, in range -180 to + 180
*/
public function rotFromRot( p_rotInput:Number ):Number
{
var rotOutput:Number = p_rotInput;
while ( rotOutput > 180 )
{
rotOutput -= 360;
}
while ( rotOutput < -180 )
{
rotOutput += 360;
}
return rotOutput;
}
/**
* @param p_radInput Angle, in radians
* @return Angle, in radians, in range 0 to ( 2 * pi )
*/
public function radFromRad( p_radInput:Number ):Number
{
var radOutput:Number = p_radInput;
while ( radOutput >= localTwoPi )
{
radOutput -= localTwoPi;
}
while ( radOutput < -localTwoPi )
{
radOutput += localTwoPi;
}
return radOutput;
}
/**
* @param p_degInput Angle, in degrees
* @return Angle, in degrees, in range -180 to +180
*/
public function rotFromDeg( p_degInput:Number ):Number
{
var rotOutput:Number = p_degInput;
while ( rotOutput > 180 )
{
rotOutput -= 360;
}
while ( rotOutput < -180 )
{
rotOutput += 360;
}
return rotOutput;
}
/**
* @param p_rotInput Angle, in degrees
* @return Angle, in degrees, in range 0 to 360
*/
public function degFromRot( p_rotInput:Number ):Number
{
var degOutput:Number = p_rotInput;
while ( degOutput >= 360 )
{
degOutput -= 360;
}
while ( degOutput < 0 )
{
degOutput += 360;
}
return degOutput;
}
/**
* @param p_radInput Angle, in radians
* @return Angle, in degrees, in range 0 to 360
*/
public function degFromRad( p_radInput:Number ):Number
{
var degOutput:Number = 180 * oneOverPi * radFromRad( p_radInput );
return degOutput;
}
/**
* @param p_degInput Angle, in degrees
* @return Angle, in radians, in range 0 to ( 2 * pi )
*/
public function radFromDeg( p_degInput:Number ):Number
{
var radOutput:Number = localPi * oneOver180 * degFromDeg( p_degInput );
return radOutput;
}
/**
* @param p_radInput Angle, in radians
* @return Angle, in degrees, in range -180 to +180
*/
public function rotFromRad( p_radInput:Number ):Number
{
return rotFromDeg( degFromRad( p_radInput ) );
}
/**
* @param p_rotInput Angle, in degrees
* @return Angle, in radians, in range 0 to ( 2 * pi )
*/
public function radFromRot( p_rotInput:Number ):Number
{
return radFromDeg( degFromRot( p_rotInput ) );
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<s:Group
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="group1_creationCompleteHandler(event)"
>
<fx:Script>
<![CDATA[
import com.cc.math.Angle;
import com.cc.math.VMath;
import com.cc.math.Vector2D;
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
import mx.states.OverrideBase;
protected var _pathBlendMode:String = BlendMode.NORMAL;
[Bindable]
public function get pathBlendMode():String { return _pathBlendMode; };
public function set pathBlendMode(value:String):void { _pathBlendMode = value; };
protected var _pathAlpha:Number = 0.5;
[Bindable]
public function get pathAlpha():Number { return _pathAlpha; };
public function set pathAlpha(value:Number):void { _pathAlpha = value; };
protected var _text:String;
[Bindable]
public function get text():String { return _text; };
public function set text(value:String):void { _text = value; };
protected var _labelStyleName:String;
[Bindable]
public function get labelStyleName():String { return _labelStyleName; };
public function set labelStyleName(value:String):void { _labelStyleName = value; };
protected var _maxLabelWidth:Number;
[Bindable]
public function get maxLabelWidth():Number { return _maxLabelWidth; };
public function set maxLabelWidth(value:Number):void { _maxLabelWidth = value; };
protected var _labelPadding:Number = 6;
[Bindable]
public function get labelPadding():Number { return _labelPadding; };
public function set labelPadding(value:Number):void { _labelPadding = value; };
protected var _extrusionAmount:Number = 200;
[Bindable]
public function get extrusionAmount():Number { return _extrusionAmount; };
public function set extrusionAmount(value:Number):void { _extrusionAmount = value; };
protected var _extrusionAngle:Number = 290; // point North East (zero is east)
[Bindable]
public function get extrusionAngle():Number { return _extrusionAngle; };
public function set extrusionAngle(value:Number):void
{
// snap the angle to within defined range avoiding right-angles and setting the quadrant
if(value <=89.999)
{
if(value < 10) value = 10;
else if(value > 80) value = 80;
quadrantNumber = 1;
}
else if(value >= 90 && value <=179.999)
{
if(value < 100) value = 100;
else if(value > 170) value = 170;
quadrantNumber = 2;
}
else if(value >= 180 && value <=269.999)
{
if(value < 190) value = 190;
else if(value > 260) value = 260;
quadrantNumber = 3;
}
else if(value >= 270)
{
if(value < 280) value = 280;
else if(value > 350) value = 350;
quadrantNumber = 4;
}
_extrusionAngle = value;
};
protected var _quadrantNumber:int = 4; // the default angle is 290 so we are in Q4
[Bindable]
public function get quadrantNumber():int { return _quadrantNumber; };
public function set quadrantNumber(value:int):void { _quadrantNumber = value; };
[Bindable]
protected var vo:Vector2D;
[Bindable]
protected var v1:Vector2D;
[Bindable]
protected var v2:Vector2D;
[Bindable]
protected var v3:Vector2D;
[Bindable]
protected var v4:Vector2D;
[Bindable]
protected var v5:Vector2D;
[Bindable]
protected var v6:Vector2D;
protected var adj:Number;
protected var theta:Number;
protected var hyp:Number;
protected var opp:Number;
protected function group1_creationCompleteHandler(event:FlexEvent):void
{
drawExtrusion();
}
//-------------------------------------------------------------
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
drawExtrusion();
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
//----------------------------------------------------------------------------
public function drawExtrusion():void
{
if(rect.width && rect.height)
{
var sv1:Vector2D;
var sv2:Vector2D;
var sv3:Vector2D;
var sv4:Vector2D;
var sv5:Vector2D;
/*
Create an origin vector for the quadrants that start at zero zero.
Quadrant 3 starts at the rect width for X and zero for Y.
*/
vo = new Vector2D(0,0);
/*
Get the length of the hypotenuse and the opposite in the extra triangle we need to draw
to extrude the triangle down to the edges.
*/
var hypLength:Number = Math.abs(rect.width/Math.cos(Angle.radFromDeg(extrusionAngle)));
var oppLength:Number = Math.abs(hypLength*Math.sin(Angle.radFromDeg(extrusionAngle)));
/*
Now create the vectors used to draw the paths based on the quadrant we are in
*/
switch(quadrantNumber)
{
case 1:
// vector ONE
v1 = new Vector2D();
v1.length = rect.width;
v1.angle = Angle.radFromDeg(0);
// vector TWO
sv2 = new Vector2D();
sv2.length = extrusionAmount+hypLength;
sv2.angle = Angle.radFromDeg(extrusionAngle);
VMath.reverse(sv2);
v2 = VMath.add(v1,sv2);
// vector THREE
sv3 = new Vector2D();
sv3.length = rect.height+oppLength;
sv3.angle = Angle.radFromDeg(90);
v3 = VMath.add(v2,sv3);
// point FOUR
sv4 = new Vector2D();
sv4.length = extrusionAmount;
sv4.angle = Angle.radFromDeg(extrusionAngle);
v4 = VMath.add(v3,sv4);
// point FIVE
sv5 = new Vector2D();
sv5.length = rect.height;
sv5.angle = Angle.radFromDeg(270);
v5 = VMath.add(v4,sv5);
break;
//-------------------
case 2:
// vector ONE
v1 = new Vector2D();
v1.length = extrusionAmount+hypLength;
v1.angle = Angle.radFromDeg(extrusionAngle);
VMath.reverse(v1);
// vector TWO
sv2 = new Vector2D();
sv2.length = rect.height+oppLength;
sv2.angle = Angle.radFromDeg(90);
v2 = VMath.add(v1,sv2);
// vector THREE
sv3 = new Vector2D();
sv3.length = extrusionAmount;
sv3.angle = Angle.radFromDeg(extrusionAngle);
v3 = VMath.add(v2,sv3);
// point FOUR
sv4 = new Vector2D();
sv4.length = rect.height;
sv4.angle = Angle.radFromDeg(270);
v4 = VMath.add(v3,sv4);
// point FIVE
sv5 = new Vector2D();
sv5.length = rect.width;
sv5.angle = Angle.radFromDeg(180);
v5 = VMath.add(v4,sv5);
break;
//-------------------
case 3:
// vector ORIGIN
vo = new Vector2D();
vo.length = rect.width;
vo.angle = 0;
// vector ONE
sv1 = new Vector2D();
sv1.length = extrusionAmount;
sv1.angle = Angle.radFromDeg(extrusionAngle);
VMath.reverse(sv1);
v1 = VMath.add(vo,sv1);
// vector TWO
sv2 = new Vector2D();
sv2.length = rect.height+oppLength;
sv2.angle = Angle.radFromDeg(90);
v2 = VMath.add(v1,sv2);
// vector THREE
sv3 = new Vector2D();
sv3.length = extrusionAmount+hypLength;
sv3.angle = Angle.radFromDeg(extrusionAngle);
v3 = VMath.add(v2,sv3);
// point FOUR
sv4 = new Vector2D();
sv4.length = rect.width;
sv4.angle = Angle.radFromDeg(0);
v4 = VMath.add(v3,sv4);
// point FIVE
sv5 = new Vector2D();
sv5.length = rect.height;
sv5.angle = Angle.radFromDeg(270);
v5 = VMath.add(v4,sv5);
break;
//-------------------
case 4:
// vector ONE
v1 = new Vector2D();
v1.length = rect.height;
v1.angle = Angle.radFromDeg(90);
// vector TWO
sv2 = new Vector2D();
sv2.length = rect.width;
sv2.angle = Angle.radFromDeg(0);
v2 = VMath.add(v1,sv2);
// vector THREE
sv3 = new Vector2D();
sv3.length = extrusionAmount+hypLength;
sv3.angle = Angle.radFromDeg(extrusionAngle);
VMath.reverse(sv3);
v3 = VMath.add(v2,sv3);
// point FOUR
sv4 = new Vector2D();
sv4.length = rect.height+oppLength;
sv4.angle = Angle.radFromDeg(270);
v4 = VMath.add(v3,sv4);
// point FIVE
sv5 = new Vector2D();
sv5.length = extrusionAmount;
sv5.angle = Angle.radFromDeg(extrusionAngle);
v5 = VMath.add(v4,sv5);
break;
//-------------------
default :
throw new Error("Incorrect quadrant calculated. Check the angle logic.");
break;
}
}
}
]]>
</fx:Script>
<!-- _______________________________________________ -->
<s:Rect
id="rect"
width="{label.width+(labelPadding*2)}"
height="{label.height+(labelPadding*2)}"
>
<s:fill>
<s:SolidColor color="{label.getStyle('backgroundColor')}" />
</s:fill>
</s:Rect>
<s:Label
id="label"
x="{labelPadding}"
y="{labelPadding}"
text="{text}"
maxWidth="{maxLabelWidth? maxLabelWidth : this.width }"
styleName="{labelStyleName}"
/>
<!-- __________________________________________________ -->
<s:Graphic>
<s:Path
id="extrusionPath"
data="
M {vo.x} {vo.y}
L {v1.x} {v1.y}
L {v2.x} {v2.y}
L {v3.x} {v3.y}
L {v4.x} {v4.y}
L {v5.x} {v5.y}
"
blendMode="{pathBlendMode}"
alpha="{pathAlpha}"
>
<s:fill>
<s:SolidColor color="#ffffff" />
</s:fill>
</s:Path>
</s:Graphic>
<!-- __________________________________________________ -->
<s:Graphic>
<s:Path
id="extrusionGreySidePath"
visible="false"
data="
M {vo.x} {vo.y}
L {v1.x} {v1.y}
L {v2.x} {v2.y}
L {v3.x} {v3.y}
"
>
<s:fill>
<s:SolidColor color="#999999" />
</s:fill>
</s:Path>
</s:Graphic>
<!-- __________________________________________________ -->
<s:Group
includeInLayout="false"
visible="false"
>
<s:Ellipse
id="dot1"
x="{v1.x-5}"
y="{v1.y-5}"
width="10"
height="10"
>
<s:fill>
<s:SolidColor color="#ff0000" />
</s:fill>
</s:Ellipse>
<s:Label text="1" x="{dot1.x+8}" y="{dot1.y}" color="#ffffff" />
<s:Ellipse
x="{v2.x-5}"
y="{v2.y-5}"
width="10"
height="10"
>
<s:fill>
<s:SolidColor color="#ff0000" />
</s:fill>
</s:Ellipse>
<s:Label text="2" x="{v2.x+8}" y="{v2.y}" color="#ffffff" />
<s:Ellipse
x="{v3.x-5}"
y="{v3.y-5}"
width="10"
height="10"
>
<s:fill>
<s:SolidColor color="#ff0000" />
</s:fill>
</s:Ellipse>
<s:Label text="3" x="{v3.x+8}" y="{v3.y}" color="#ffffff" />
<s:Ellipse
x="{v4.x-5}"
y="{v4.y-5}"
width="10"
height="10"
>
<s:fill>
<s:SolidColor color="#ff0000" />
</s:fill>
</s:Ellipse>
<s:Label text="4" x="{v4.x+8}" y="{v4.y}" color="#ffffff" />
<s:Ellipse
x="{v5.x-5}"
y="{v5.y-5}"
width="10"
height="10"
>
<s:fill>
<s:SolidColor color="#ff0000" />
</s:fill>
</s:Ellipse>
<s:Label text="5" x="{v5.x+8}" y="{v5.y}" color="#ffffff" />
</s:Group>
</s:Group>
package com.cc.layout
{
import com.cc.components.TextBlockExtruded;
import mx.collections.ArrayCollection;
import mx.core.ILayoutElement;
import mx.core.IVisualElement;
import spark.components.supportClasses.GroupBase;
import spark.layouts.supportClasses.LayoutBase;
public class TextBlockExtrudedVerticalLayout extends LayoutBase
{
protected var _quadrantNumber:int = 1; // the default angle is 290 so we are in Q4
[Bindable]
public function get quadrantNumber():int { return _quadrantNumber; };
public function set quadrantNumber(value:int):void { _quadrantNumber = value; };
protected var _gap:Number = 10;
[Bindable]
public function get gap():Number { return _gap; };
public function set gap(value:Number):void { _gap = value; };
override public function updateDisplayList(width:Number, height:Number):void
{
var layoutTarget:GroupBase = target;
var i:int;
var count:int = layoutTarget.numElements;
var element:ILayoutElement;
var elementWidth:Number;
var elementHeight:Number;
if(count>0)
{
element = layoutTarget.getElementAt(0);
if(element is TextBlockExtruded)
{
quadrantNumber = TextBlockExtruded(element).quadrantNumber;
}
else throw new Error("This layout has non-TextBlockExtruded instances in it! Boo!");
for(i = 0; i<count; i++)
{
element = layoutTarget.getElementAt(i);
element.setLayoutBoundsSize(NaN,NaN);
// check it is a TextBlockExtruded instance
if(element is TextBlockExtruded)
{
elementHeight = TextBlockExtruded(element).rect.height;
}
else throw new Error("This layout has non-TextBlockExtruded instances in it! Boo!");
var g:Number = i==0? 0 : gap;
element.setLayoutBoundsPosition(0,i*(elementHeight+g));
}
// swap depths if in quadrant 1 or 2
if(quadrantNumber == 1 || quadrantNumber == 2)
{
i = 0;
while(i<count)
{
// get the top element
element = layoutTarget.getElementAt(i) as TextBlockExtruded;
if (element is IVisualElement)
IVisualElement(element).depth = Math.abs(i-(count-1));
i++;
}
}
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:components="com.cc.components.*"
xmlns:layout="com.cc.layout.*"
minWidth="955"
minHeight="600"
initialize="application1_initializeHandler(event)"
creationComplete="application1_creationCompleteHandler(event)"
backgroundColor="#ffffff"
>
<fx:Script>
<![CDATA[
import com.cc.components.TextBlockExtruded;
import flash.text.engine.FontLookup;
import flash.text.engine.RenderingMode;
import flash.text.engine.TextLine;
import flash.text.engine.TypographicCase;
import flashx.textLayout.compose.TextFlowLine;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.conversion.TextConverter;
import flashx.textLayout.elements.Configuration;
import flashx.textLayout.elements.OverflowPolicy;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.events.CompositionCompleteEvent;
import flashx.textLayout.events.FlowOperationEvent;
import flashx.textLayout.events.UpdateCompleteEvent;
import flashx.textLayout.factory.StringTextLineFactory;
import flashx.textLayout.formats.TextAlign;
import flashx.textLayout.formats.TextDecoration;
import flashx.textLayout.formats.TextLayoutFormat;
import mx.collections.ArrayCollection;
import mx.core.IVisualElement;
import mx.events.FlexEvent;
import spark.events.TextOperationEvent;
import spark.utils.TextFlowUtil;
protected var config:Configuration;
protected var defaultFormat:TextLayoutFormat;
protected var elementCollection:ArrayCollection;
protected function application1_initializeHandler(event:FlexEvent):void
{
config = Configuration(TextFlow.defaultConfiguration).clone();
defaultFormat = new TextLayoutFormat();
defaultFormat.paddingTop = 6;
defaultFormat.color = 0x666666;
defaultFormat.textAlign = TextAlign.LEFT;
defaultFormat.textAlignLast = TextAlign.LEFT;
defaultFormat.fontSize = 32;
defaultFormat.lineHeight = 38;
config.textFlowInitialFormat = defaultFormat;
textFlow.format = defaultFormat;
}
//-------------------------------------------------
protected function compositionCompleteHandler(event:CompositionCompleteEvent):void
{
var eTextFlow:TextFlow = event.textFlow;
if(eTextFlow.flowComposer)
{
if(!elementCollection) elementCollection = new ArrayCollection();
elementCollection.removeAll();
var i:int = 0;
var j:int = eTextFlow.flowComposer.numLines;
if(j>0)
{
for(i = 0; i<j; i++)
{
var textLine:TextFlowLine = eTextFlow.flowComposer.getLineAt(i);
var tf:TextFlow = textLine.paragraph.getTextFlow();
var textOfLine:String = tf.getText(textLine.absoluteStart, textLine.absoluteStart + textLine.textLength);
//trace(textOfLine);
if (textLine)
{
var rx:int = textLine.getBounds().x;
var ry:int = textLine.getBounds().y;
var rw:int = textLine.getBounds().width;
var rh:int = textLine.getBounds().height;
}
//trace("rx:"+rx+" /ry:"+ry+" /rw:"+rw+" /rh:"+rh);
addTextLine(textOfLine);
}
}
buildLayout();
}
}
//-------------------------------------------------
protected function addTextLine(textOfLine:String):void
{
var extrudedText:TextBlockExtruded = new TextBlockExtruded();
extrudedText.text = textOfLine;
extrudedText.extrusionAmount = extrusionAmountSlider.value;
extrudedText.extrusionAngle = extrusionAngleSlider.value;
extrudedText.maxLabelWidth = 350;
elementCollection.addItem(extrudedText);
}
//-------------------------------------------------
protected function slider_changeHandler(event:Event):void
{
buildLayout();
}
//-------------------------------------------------
protected function buildLayout():void
{
extrudedTextGroup.removeAllElements();
var i:int = 0;
var j:int = elementCollection.length;
for(i;i<j;i++)
{
var e:TextBlockExtruded = elementCollection.getItemAt(i) as TextBlockExtruded;
e.depth = i;
e.extrusionAngle = extrusionAngleSlider.value;
e.extrusionAmount = extrusionAmountSlider.value;
e.pathBlendMode = eraseModeBtn.selected? BlendMode.ERASE : BlendMode.NORMAL;
e.pathAlpha = extrusionAlphaSlider.value;
e.drawExtrusion();
extrudedTextGroup.addElement(e);
}
}
//-------------------------------------------------
protected function textInputChangeBtn_clickHandler(event:MouseEvent):void
{
textFlow.removeEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE,compositionCompleteHandler);
var tf:TextFlow = TextConverter.importToFlow(inputText.text, TextConverter.PLAIN_TEXT_FORMAT);
tf.addEventListener(CompositionCompleteEvent.COMPOSITION_COMPLETE,compositionCompleteHandler);
textFlowView.textFlow = tf;
textFlowView.textFlow.format = defaultFormat;
}
]]>
</fx:Script>
<s:BitmapImage
source="@Embed(source='assets/images/lastminute_bg.jpg')"
width="100%"
height="100%"
alpha="0.5"
/>
<s:Group
id="extrusionGroupContainer"
width="100%"
height="100%"
blendMode="layer"
>
<s:BitmapImage
source="@Embed(source='assets/images/lastminute_bg.jpg')"
width="100%"
height="100%"
/>
<s:Group
id="extrudedTextGroup"
width="100%"
height="100%"
top="100"
left="260"
>
<s:layout>
<layout:TextBlockExtrudedVerticalLayout />
</s:layout>
</s:Group>
</s:Group>
<s:Panel
id="panel"
width="100%"
height="240"
left="20"
right="20"
bottom="20"
title="EXTRUDED TEXT WORKSHOP"
>
<s:layout>
<s:VerticalLayout
gap="80"
paddingBottom="20"
paddingLeft="20"
paddingRight="20"
paddingTop="20"
/>
</s:layout>
<s:Group>
<s:HGroup
gap="20"
width="100%"
height="100%"
>
<s:VGroup>
<s:TextInput
id="inputText"
text="Feel free to change the text. This will create extruded text blocks. Line breaks are determined by the TextFlow area."
maxChars="250"
width="350"
/>
<s:Button
id="textInputChangeBtn"
label="CHANGE TEXT"
click="textInputChangeBtn_clickHandler(event)"
enabled="{inputText.text!=''}"
/>
<s:HGroup
id="controls"
paddingTop="40"
>
<s:VGroup>
<s:Label text="EXTRUSION ANGLE" />
<s:HSlider
id="extrusionAngleSlider"
stepSize="1"
minimum="0"
maximum="360"
value="290"
change="slider_changeHandler(event)"
/>
</s:VGroup>
<s:VGroup
height="100%"
>
<s:Label text="EXTRUSION AMOUNT" />
<s:HSlider
id="extrusionAmountSlider"
stepSize="1"
minimum="10"
maximum="1000"
value="500"
change="slider_changeHandler(event)"
/>
</s:VGroup>
<s:VGroup
height="100%"
>
<s:Label text="EXTRUSION ALPHA" />
<s:HSlider
id="extrusionAlphaSlider"
stepSize="0.01"
minimum="0"
maximum="1"
value="0.50"
change="slider_changeHandler(event)"
/>
</s:VGroup>
<s:VGroup
height="100%"
>
<s:Label text="ERASE MODE" />
<s:ToggleButton
id="eraseModeBtn"
label="{eraseModeBtn.selected? 'ON' : 'OFF'}"
click="slider_changeHandler(event)"
/>
</s:VGroup>
</s:HGroup>
</s:VGroup>
<s:TextArea
id="textFlowView"
width="350"
height="150"
skinClass="com.cc.skins.BlankTextAreaSkin"
>
<s:textFlow>
<s:TextFlow
id="textFlow"
compositionComplete="compositionCompleteHandler(event)"
>
<s:p>TOP SECRET</s:p>
<s:p>Hey lastminute_com just back from Rome ... casual #prouding pose at the colosseum</s:p>
<s:p>#PROUDING</s:p>
</s:TextFlow>
</s:textFlow>
</s:TextArea>
</s:HGroup>
</s:Group>
</s:Panel>
</s:Application>
package com.cc.math
{
/**
* A basic 2-dimensional vector class.
*/
public class Vector2D
{
[Bindable]
public var x:Number;
[Bindable]
public var y:Number;
public var isNormalized:Boolean;
public function Vector2D(_x:Number = 0, _y:Number = 0)
{
x = _x;
y = _y;
}
/**
* Generates a copy of this vector.
* @return Vector2D A copy of this vector.
*/
public function clone():Vector2D
{
return new Vector2D(x, y);
}
/**
* Whether or not this vector is equal to zero, i.e. its x, y, and length are zero.
* @return Boolean True if vector is zero, otherwise false.
*/
public function isZero():Boolean
{
return x == 0 && y == 0;
}
public function set length(value:Number):void
{
var a:Number = angle;
x = Math.cos(a) * value;
y = Math.sin(a) * value;
}
public function get length():Number
{
return Math.sqrt(x * x + y * y);
}
public function set angle(value:Number):void
{
var len:Number = length;
x = Math.cos(value) * len;
y = Math.sin(value) * len;
}
public function get angle():Number {
return Math.atan2(y, x);
}
/**
* Calculates the dot product of this vector and another given vector.
* @param v2 Another Vector2D instance.
* @return Number The dot product of this vector and the one passed in as a parameter.
*/
public function dotProd(v2:Vector2D):Number {
return x * v2.x + y * v2.y;
}
/**
* Calculates the cross product of this vector and another given vector.
* @param v2 Another Vector2D instance.
* @return Number The cross product of this vector and the one passed in as a parameter.
*/
public function crossProd(v2:Vector2D):Number
{
return x * v2.y - y * v2.x;
}
/**
* Calculates the angle between two vectors.
* @param v1 The first Vector2D instance.
* @param v2 The second Vector2D instance.
* @return Number the angle between the two given vectors.
*/
public static function angleBetween(v1:Vector2D, v2:Vector2D):Number
{
if(!v1.isNormalized) v1 = v1.clone().normalize();
if(!v2.isNormalized) v2 = v2.clone().normalize();
return Math.acos(v1.dotProd(v2));
}
/**
* Determines if a given vector is to the right or left of this vector.
* @return int If to the left, returns -1. If to the right, +1.
*/
public function sign(v2:Vector2D):int
{
return perp.dotProd(v2) < 0 ? -1 : 1;
}
/**
* Finds a vector that is perpendicular to this vector.
* @return Vector2D A vector that is perpendicular to this vector.
*/
public function get perp():Vector2D
{
return new Vector2D(-y, x);
}
/**
* Calculates the distance from this vector to another given vector.
* @param v2 A Vector2D instance.
* @return Number The distance from this vector to the vector passed as a parameter.
*/
public function dist(v2:Vector2D):Number {
return Math.sqrt(distSQ(v2));
}
/**
* Calculates the distance squared from this vector to another given vector.
* @param v2 A Vector2D instance.
* @return Number The distance squared from this vector to the vector passed as a parameter.
*/
public function distSQ(v2:Vector2D):Number
{
var dx:Number = v2.x - x;
var dy:Number = v2.y - y;
return dx * dx + dy * dy;
}
/**
* Brings the given vector's magnitude down to a 0-1 value so it can be used to constrain other vectors.
*/
public function normalize():Vector2D
{
var vx:Number = x;
var vy:Number = y;
var len:Number = Math.sqrt((vx * vx) + (vy * vy));
if (len)
{
x /= len;
y /= len;
}
isNormalized = true;
return this;
}
/**
* Returns a String description of the Vector2D object.
*/
public function toString():String
{
return "x:"+x.toFixed(2)+" / y:"+y.toFixed(2)+" / length: "+length.toFixed(2)+" / angle: "+angle.toFixed(2);
}
}
}
package com.cc.math
{
/**
* Static class that has some of the vector math functions for behaviors.
*/
public class VMath {
/**
* Calculates the distance squared from first vector to second vector.
* @param v1 The Vector2D in question
* @param v2 The Vector2D to be added to v1
*/
static public function add(v1:Vector2D, v2:Vector2D):Vector2D
{
var v:Vector2D = new Vector2D();
v.x = v1.x + v2.x;
v.y = v1.y + v2.y;
return v;
}
/**
* Calculates the distance squared from first vector to second vector.
*/
static public function subtract(v1:Vector2D, v2:Vector2D):Vector2D
{
var v:Vector2D = new Vector2D();
v.x = v1.x - v2.x;
v.y = v1.y - v2.y;
return v;
}
/**
* Multiplies the two vectors by a value, creating a new Vector2D instance to hold the result.
* @param v2 A Vector2D instance.
* @param value The value to multiply the vector instance by.
*/
static public function multiply(v1:Vector2D, value:Number):Vector2D
{
var v:Vector2D = new Vector2D();
v.x = v1.x * value;
v.y = v1.y * value;
return v;
}
/**
* Divides this vector by a value, creating a new Vector2D instance to hold the result.
* @param v2 A Vector2D instance.
* @param value The value to multiply the vector instance by.
*/
static public function divide(v1:Vector2D, value:Number):Vector2D
{
var v:Vector2D = new Vector2D();
v.x = v1.x / value;
v.y = v1.y / value;
return v;
}
/**
* Brings the given vector's magnitude down to a 0-1 value so it can be used to constrain other vectors.
* @param v A Vector2D instance.
*/
static public function normalize(v:Vector2D):void
{
var vx:Number = v.x;
var vy:Number = v.y;
var len:Number = Math.sqrt((vx * vx) + (vy * vy));
if (len)
{
v.x /= len;
v.y /= len;
}
v.isNormalized = true;
}
/**
* Ensures the length of the vector is no longer than the given value.
* @param v A Vector2D instance to truncate.
* @param max The maximum value this vector should be. If length is larger than max, it will be truncated to this value.
*/
static public function truncate(v:Vector2D, max:Number):void {
var vx:Number = v.x;
var vy:Number = v.y;
var cl:Number = Math.sqrt((vx * vx) + (vy * vy));
var nl:Number = Math.min(max, cl);
var a:Number = Math.atan2(vy, vx);
v.x = Math.cos(a) * nl;
v.y = Math.sin(a) * nl;
}
static public function reverse(v:Vector2D):void {
v.x = -v.x;
v.y = -v.y;
}
} // end class
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment