/**
* Copyright jules ( http://wonderfl.net/user/jules )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/aPSG
*/
package {
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
public class CubicBezierCurve extends Sprite
{
private var lineHolder:Sprite = new Sprite();
private var pencilHolder:Sprite = new Sprite();
private var line:Sprite;
private var bezierLine:CubicBezier;
private var pencilPoint:PencilPoint;
private var button:Sprite;
/**
* Constructor
* - Add containers
* - Add points to the stage to start with
* - Add a button to turn off the anchorpoints/controlpoints
*/
public function CubicBezierCurve()
{
stage.frameRate = 25;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
//Add containers
addChild(lineHolder);
addChild(pencilHolder);
//Points on the left
for (var i:int; i<10;i++) {
pencilPoint = new PencilPoint(100+i,200+i);
pencilHolder.addChild(pencilPoint);
pencilPoint.addEventListener(MouseEvent.MOUSE_DOWN, pencilDown);
pencilPoint.addEventListener(MouseEvent.MOUSE_UP, pencilUp);
//Points on the right
pencilPoint = new PencilPoint(350,50);
pencilHolder.addChild(pencilPoint);
pencilPoint.addEventListener(MouseEvent.MOUSE_DOWN, pencilDown);
pencilPoint.addEventListener(MouseEvent.MOUSE_UP, pencilUp);
drawLine(new Event(Event.ENTER_FRAME));
}
//Add a button to turn off the anchorPoints and pencilPoints
button = new Sprite();
button.graphics.beginFill(0xFFCCAA);
button.graphics.drawRoundRect(20,23,100,20,10);
button.graphics.endFill();
addChild(button);
var hideText:TextField = new TextField();
hideText.text = "Hide Anchors"
hideText.x=25;
hideText.y=25;
hideText.height=18;
hideText.selectable=false;
addChild(hideText);
hideText.addEventListener(MouseEvent.CLICK,hidePencils);
}
/**
* Add's a new point to the line at the clicked possition
*/
private function addAnchor(evt:MouseEvent):void
{
pencilPoint = new PencilPoint(evt.localX,evt.localY);
var index:Number = parseInt(evt.target.name)+1;
pencilHolder.addChildAt(pencilPoint, index);
pencilPoint.addEventListener(MouseEvent.MOUSE_DOWN, pencilDown);
pencilPoint.addEventListener(MouseEvent.MOUSE_UP, pencilUp);
//Update the line
drawLine(new Event(Event.ENTER_FRAME));
}
/**
* redraw's the line when the pencilPoint is changing possition
*/
private function pencilDown(evt:MouseEvent):void
{
this.addEventListener(Event.ENTER_FRAME, drawLine);
}
/**
* Stops redrawing the line
*/
private function pencilUp(evt:MouseEvent):void
{
//First make sure the line is redrawn
this.dispatchEvent(new Event(Event.ENTER_FRAME));
this.removeEventListener(Event.ENTER_FRAME, drawLine);
}
/**
* Draw's a bezierCurved line onEnterFrame while a pencilpoint is clicked
*
* @todo each line will be updated during this function. update only line's that might be changed so you can boost performance
*
*/
private function drawLine(evt:Event):void
{
//Remove all excisting lines
removeAllLines(lineHolder);
var next:Boolean;
var anchorChildren:Number=pencilHolder.numChildren;
//Update all lines between anchor points (and skip the last point cause there's no line to draw from that point)
//@todo don't do this for all anchor points
for (var i:Number=0; i<anchorChildren-1; i++){
//Make reference holders so we don't have to type to much when we need them
var anchor:Sprite;
var control:Sprite;
var nextAnchor:Sprite;
var nextControl:Sprite;
//Store easy references
anchor = Sprite(Sprite(pencilHolder.getChildAt(i)).getChildByName("anchor"));
control = Sprite(Sprite(pencilHolder.getChildAt(i)).getChildByName("point2"));
nextAnchor = Sprite(Sprite(pencilHolder.getChildAt(i+1)).getChildByName("anchor"));
nextControl = Sprite(Sprite(pencilHolder.getChildAt(i+1)).getChildByName("point1"));
//Draw the curve
bezierLine = new CubicBezier(anchor,nextAnchor,control,nextControl);
line = new Sprite();
line.addChild(bezierLine.draw());
//Give curve a name, so we can use it later to determine which curve was clicked and needs to be subdivided
line.name=""+i;
//Add curve to the stage
lineHolder.addChild(line);
line.addEventListener(MouseEvent.CLICK, addAnchor);
}
}
/**
* removeAllLines in a container and clear up it's listeners
*/
private function removeAllLines(_sprite:Sprite):void
{
while (_sprite.numChildren > 0){
_sprite.getChildAt(_sprite.numChildren-1).removeEventListener(MouseEvent.CLICK, addAnchor);
_sprite.removeChildAt(_sprite.numChildren-1);
}
}
/**
* Make pencilPoints invisible
*
* @todo remove eventlisteners / disable pencils while they are invisible
*/
private function hidePencils(evt:MouseEvent):void
{
//Alpha is 1 or 0, in other words true or false, so inverse it's state
pencilHolder.alpha=Number(!pencilHolder.alpha);
}
}
}
import flash.display.Sprite;
class Point extends Sprite
{
private var point:Sprite;
/**
* Draws a simple point on a given possition with a given radius and color
*
* @todo get rid of setting point.x but use this.x
*/
public function Point(_x:Number=0, _y:Number=0, _radius:Number=2, _color:uint=0x00FFFF){
super();
point = new Sprite();
point.graphics.beginFill(_color);
point.graphics.drawCircle(0,0,_radius);
point.x=_x;
point.y=_y;
addChild(point);
}
public override function set x(_x:Number):void
{
point.x = _x;
}
public override function set y(_y:Number):void
{
point.y = _y;
}
public override function get x():Number
{
return point.x;
}
public override function get y():Number
{
return point.y;
}
}
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
class PencilPoint extends Sprite
{
private var anchorPoint :Sprite;
private var controlPoint1 :Sprite;
private var controlPoint2 :Sprite;
private var line :Shape;
private var moveControlPoint :Boolean=false;
private var moveControlPoint1XDistance :Number;
private var moveControlPoint1YDistance :Number;
private var moveControlPoint2XDistance :Number;
private var moveControlPoint2YDistance :Number;
private var rotationPoint :Sprite;
/**
* Constructor
*
* Draw a new pencil point on a given possition.
* Each pencil point contains 1 anchorPoint and 2 controlPoints
*
* @note The new Point(); that's used in this function is a custom written class, so don't mix this up with the default way to make a new Point
*/
public function PencilPoint(_x:Number=0, _y:Number=0){
super();
//Create a new anchorPoint
anchorPoint = new Point(_x,_y,4,0xFFFF00);
//Give it a name so we can access it by name
anchorPoint.name = "anchor";
addChild(anchorPoint);
//Add eventListeners to make them dragable
anchorPoint.addEventListener(MouseEvent.MOUSE_DOWN,startMove);
anchorPoint.addEventListener(MouseEvent.MOUSE_UP,stopMove);
/**
* Repeate the abbove for two controlPoints
*/
controlPoint1 = new Point(_x,_y);
controlPoint1.name = "point1";
addChild(controlPoint1);
controlPoint1.addEventListener(MouseEvent.MOUSE_DOWN,startMove);
controlPoint1.addEventListener(MouseEvent.MOUSE_UP,stopMove);
controlPoint2 = new Point(_x,_y);
controlPoint2.name = "point2";
addChild(controlPoint2);
controlPoint2.addEventListener(MouseEvent.MOUSE_DOWN,startMove);
controlPoint2.addEventListener(MouseEvent.MOUSE_UP,stopMove);
}
/**
* Called by an eventListener which listens if a anchorPoint or a controlPoint is pressed
*
* @todo add a seperate listener when an anchor is pressed
*/
private function startMove(evt:MouseEvent):void
{
if(evt.target.parent.name == "anchor"){
/**
* When the anchor starts to drag, set these settings so controlpoint will follow it's movements
* You can do this by storing distance between anchorPoint and controlpoint
* This will be used later on at the enterFrame
*/
moveControlPoint = true;
moveControlPoint1XDistance = controlPoint1.x - anchorPoint.x;
moveControlPoint1YDistance = controlPoint1.y - anchorPoint.y;
moveControlPoint2XDistance = controlPoint2.x - anchorPoint.x;
moveControlPoint2YDistance = controlPoint2.y - anchorPoint.y;
}
//Reference the point which is moving so we can access this onEnterFrame
rotationPoint = Sprite(evt.target);
//Start dragging the point
Sprite(evt.target).startDrag();
//Listen onEnterFrame while dragging
this.addEventListener(Event.ENTER_FRAME, drawLine);
}
/**
* Stop dragging and stop listeners
*/
private function stopMove(evt:MouseEvent):void
{
if(evt.target.parent.name == "anchor") moveControlPoint = false;
Sprite(evt.target).stopDrag();
//Prevent there wasn't a new ENTER_FRAME fast enough to update the line to the current positions
this.dispatchEvent(new Event(Event.ENTER_FRAME));
this.removeEventListener(Event.ENTER_FRAME, drawLine);
}
/**
* - Draws a line from the each controlPoint to the anchorPoint
* - Rotates the opposite controlPoint, so they keep exactly in each others opposites possition
* - moves control points when the anchorPoint is moving
*
* @todo make an seperate listener function to update a moving anchorPoint
*/
private function drawLine(evt:Event):void
{
//Only remove lines, not the anchor points which are in the same container (this)
if(this.numChildren>3) removeChildAt(0);
if(moveControlPoint){
//Track movements of an anchor
controlPoint1.x = anchorPoint.x + moveControlPoint1XDistance;
controlPoint1.y = anchorPoint.y + moveControlPoint1YDistance;
controlPoint2.x = anchorPoint.x + moveControlPoint2XDistance;
controlPoint2.y = anchorPoint.y + moveControlPoint2YDistance;
}
var radius:Number;
var rotation:Number;
if(rotationPoint.parent.name=="point1"){
//Get radius and rotation
radius = getRadius(controlPoint1,anchorPoint);
rotation = getRotation(controlPoint1,anchorPoint);
//Rotate the opposite point
setRotation(controlPoint2,anchorPoint,rotation+90);
} else if(rotationPoint.parent.name=="point2"){
//Get radius and totation
radius = getRadius(controlPoint2,anchorPoint);
rotation = getRotation(controlPoint2,anchorPoint);
//Rotate opposite point
setRotation(controlPoint1,anchorPoint,rotation+90);
}
//Draw line between controlPoints and the central anchorPoint
line = new Shape();
line.graphics.lineStyle(1,0xFF3333);
line.graphics.moveTo(controlPoint1.x,controlPoint1.y);
line.graphics.lineTo(anchorPoint.x,anchorPoint.y);
line.graphics.lineTo(controlPoint2.x,controlPoint2.y);
addChildAt(line,0);
}
/**
* Calculates the radius of point A, compared with pointB
*
* @todo This function should be added to a custom Math class
*/
private function getRadius(pointA:Sprite,pointB:Sprite):Number
{
var width:Number;
var height:Number;
if(pointA.x>pointB.x){
width = pointA.x - pointB.x;
} else {
width = pointB.x - pointA.x;
}
if(pointA.y>pointB.y){
height = pointA.y - pointB.y;
} else {
height = pointB.y - pointA.y;
}
return Math.sqrt(Math.pow(width,2) + Math.pow(height,2) );
}
/**
* Calculates the rotation between a point (controlPoint) and the centerPoint (anchorPoint)
*
* @todo This function should be added to a custom Math class
*/
private function getRotation(point:Sprite, centerPoint:Sprite):Number
{
var widthA:Number;
var widthB:Number;
var rotation:Number;
if(centerPoint.x>point.x){
widthA = centerPoint.x-point.x;
} else {
widthA = point.x-centerPoint.x;
}
if(centerPoint.y>point.y){
widthB = centerPoint.y-point.y;
} else {
widthB = point.y-centerPoint.y;
}
//Rotation between 0 - 90
rotation = Math.atan(widthA/widthB) * (180/Math.PI);
//Calculate the real rotation depending in which quadrant the point is possitioned
if(point.y >= centerPoint.y && point.x >= centerPoint.x){
rotation = 90 + ( 90 - rotation );
} else if (point.y >= centerPoint.y && point.x <= centerPoint.x){
rotation =180 + rotation;
} else if(point.y <= centerPoint.y && point.x <= centerPoint.x){
rotation= 270 + ( 90 - rotation );
}
//Be sure there's a possitive rotation
if(!rotation > 0){
rotation = 0;
}
return rotation;
}
/**
* Rotates a point compared to a centerPoint (anchorPoint) with a given angle
*/
private function setRotation(point:Sprite, centerPoint:Sprite, angle:Number):void
{
angle=angle*Math.PI/180;
var radius:Number = getRadius(point,centerPoint);
point.x = Math.cos(angle) * radius + centerPoint.x;
point.y = Math.sin(angle) * radius + centerPoint.y;
}
}
import flash.display.Shape;
import flash.display.Sprite;
class CubicBezier
{
// Default values
private var segments:Number = 100;
private var color:uint = 0x000000;
private var thikness:Number = 2;
//Reference objects
private var anchor1:Sprite;
private var anchor2:Sprite;
private var control1:Sprite;
private var control2:Sprite;
/**
* Constructor
*
* used to store references to anchor points and control points
*/
public function CubicBezier(anchorPoint1:Sprite,anchorPoint2:Sprite,controlPoint1:Sprite,controlPoint2:Sprite)
{
anchor1=anchorPoint1;
anchor2=anchorPoint2;
control1=controlPoint1;
control2=controlPoint2;
}
/**
* Draws a bezier curve by dividing it up in the predefined segments and draws a line between them
*
* @todo find a way to use less segments and use curveTo to draw the line
*/
public function draw():Shape
{
var step:Number = 1/segments;
var line:Shape = new Shape();
line.graphics.lineStyle(thikness,color);
line.graphics.moveTo(anchor1.x,anchor1.y);
var posx:Number;
var posy:Number;
//This loops draws each step of the curve
for (var u:Number = 0; u <= 1; u += step) {
posx = Math.pow(u,3)*(anchor2.x+3*(control1.x-control2.x)-anchor1.x)+3*Math.pow(u,2)*(anchor1.x-2*control1.x+control2.x)+3*u*(control1.x-anchor1.x)+anchor1.x;
posy = Math.pow(u,3)*(anchor2.y+3*(control1.y-control2.y)-anchor1.y)+3*Math.pow(u,2)*(anchor1.y-2*control1.y+control2.y)+3*u*(control1.y-anchor1.y)+anchor1.y;
line.graphics.lineTo(posx,posy);
}
//As a final step, make sure the curve ends on the second anchor
line.graphics.lineTo(anchor2.x,anchor2.y);
return line;
}
/**
* Set number of segments
*/
public function setSegments(_segments:Number):void
{
segments=_segments;
}
/**
* Set color
*/
public function setColor(_color:uint):void
{
color=_color;
}
/**
* Set thikness
*/
public function setThikness(_thikness:Number):void
{
thikness=_thikness;
}
}