Drawing Bezier tool using Robotlegs

I came back from FITC Toronto with a lot of ideas for new posts and this is the last one of them. But fear not this is a first article in what will probably be a serie of 3 because it would otherwise be too long (or I wouldn’t have the patience to write it). So while at FITC, I went to a presentation be the guys at Firstborn about how they were often making tools instead of doing things by brute force. Well the idea stuck with me.

In the current project that I am working on, there was a part where I needed the coordinates of points along a path. The brute force way was to estimate the next point myself and to compile to see if I was right, repeat until I had all the coordinates I needed. Very tedious and boring task and the path could change often so there was high chances that I would redo this process often. What better time to start making a tool! Well it turn out that my project changed so much that that part wasn’t in it anymore… But it still makes a great topic for this blog.

Let me start by showing you what will be the result of this first post. (Below is not juste whitespace, click in it to ad points. You can select a path to make a control point appear, drag the control point to make a curve).

As you can see this is pretty bare bone. But the good thing about that is that you can use this as the base of multiple tools.

I built this using Robotlegs. If I am going to build something for myself, might as well learn (or train) a few things on the way. Plus, I think Robotlegs is very well suited for application style projects. Now that being said, using that kind of framework (MVC) will require you to create a lot of extra classes but in the end you will understand what you gain by doing so. Out of all these, 4 of them are really important. The Model, where you will keep all information on paths and points at all time and three View classes; one for the clickable area layer, one for the paths layer and one for the point layer.

The easiest of all of them is the clickable are layer. It’s job is just to register clicks and tell the framework where something has been clicked. This could have been done otherwise, but since we will want to layer stuff (points are over paths) plus we will want to select points and path to move or curve them, it is just easier to create a view just to register clicks on the unused stage and put that view in the back off our application.

package com.zedia.drawingtool.view.components {
  import com.zedia.drawingtool.events.PointEvent;
  import com.zedia.drawingtool.model.objects.PointObject;
  import flash.display.Sprite;
  import flash.events.MouseEvent;
 
  /**
    * @author dominicg
  */
  public class DrawingArea extends Sprite {
    public var pointArray:Array;
    private var _pathArray:Array;
    public function DrawingArea() {
      graphics.beginFill(0xffffff);
      graphics.drawRect(0, 0, 550, 400);
      graphics.endFill();
      addEventListener(MouseEvent.MOUSE_DOWN, _onMouseClick, false, 0, true);
    }
    private function _onMouseClick(event : MouseEvent) : void {
      dispatchEvent(new PointEvent(PointEvent.ADD_POINT, new PointObject(event.stageX, event.stageY)));
    }
  }
}

Our second view is the one that handles the points. Points are simple visual objects, they are just circles placed at a x and y coordinate. So when the user clicks on the clickable layer, the point view is notified and a circle is added where the click was registered. Another functionality that is added is that you can drag a point to move it around the stage. One thing to notice is that whenever a point is moved, it tells the framework about it so that the Model is always up to date and so that the path layer can display the paths correctly.

package com.zedia.drawingtool.view.components {
	import com.zedia.drawingtool.events.PointEvent;
	import com.zedia.drawingtool.model.objects.PointObject;
 
	import flash.display.Sprite;
	import flash.events.Event;
 
	/**
	 * @author dominicg
	 */
	public class PointLayer extends Sprite {
		private var _pointVector:Vector.<PathPoint>;
		public function PointLayer() {
			_pointVector = new Vector.<PathPoint>();
		}
		public function addPoint(point:PointObject):void{
			var pathPoint:PathPoint = new PathPoint();
			pathPoint.addEventListener(PointEvent.POINT_MOVED, _onPointMoved, false, 0, true);
			pathPoint.x = point.x;
			pathPoint.y = point.y;
			addChild(pathPoint);
			_pointVector.push(pathPoint);
		}
 
		private function _onPointMoved(event:Event) : void {
			dispatchEvent(new PointEvent(PointEvent.POINT_MOVED, new PointObject(PathPoint(event.target).x, PathPoint(event.target).y, _pointVector.indexOf(PathPoint(event.target)))));
		}
	}
}

Now this is the last of the view class: the PathLayer. It is also the most complicated of the three view classes because a path is a complex object. It is comprised of a start point, an end point and a control point. With those you can draw a curve using the curveTo method from the AS3 drawing API. Here is the code:

package com.zedia.drawingtool.view.components {
	import com.zedia.drawingtool.events.PathEvent;
	import com.zedia.drawingtool.model.objects.PointObject;
	import com.zedia.drawingtool.model.objects.PathObject;
 
	import flash.display.Sprite;
	import flash.events.Event;
 
	/**
	 * @author dominicg
	 */
	public class PathLayer extends Sprite {
		private var _pathVector:Vector.<Path>;
		private var _selected:int = -1;
		public function PathLayer() {
			_pathVector = new Vector.<Path>();
		}
		public function addPath(pathObject:PathObject):void{
			var path:Path = new Path(pathObject.firstPoint, pathObject.secondPoint, pathObject.controlPoint);
			path.addEventListener(PathEvent.PATH_CLICKED, _onPathClicked, false, 0, true);
			path.addEventListener(PathEvent.CONTROL_POINT_MOVED, _onControlPointMoved, false, 0, true);
			addChild(path);
			_pathVector.push(path);	
		}
 
		private function _onControlPointMoved(event : Event) : void {
			dispatchEvent(new PathEvent(PathEvent.CONTROL_POINT_MOVED, new PathObject(new PointObject(0,0,0), new PointObject(0,0,0), _pathVector.indexOf(Path(event.target)), Path(event.target).controlPoint)));
		}
 
		private function _onPathClicked(event : Event) : void {
			if (_selected > -1){
				_pathVector[_selected].deselect();
			}
			_selected = _pathVector.indexOf(Path(event.target));
		}
 
		public function updatePaths(updatedPathVector : Vector.<PathObject>) : void {
			for (var i : int = 0; i < updatedPathVector.length; i++) {
				_pathVector[updatedPathVector[i].id].update(updatedPathVector[i]);
			}
		}
		public function deselectAll():void{
			if (_selected > -1){
				_pathVector[_selected].deselect();
				_selected = -1;
			}
 
		}
	}
}

You will find more information about paths in the Path class inside the view folder.

Finally the last important class is the Model. This is where you keep information about the state of the application. With the information stored in the Model you can recreate exactly how the application is right now, which is really practical if you want to save the state to a file or export data. As you will see, it is mostly saving a data representation of visual objects in our views (points and paths).

package com.zedia.drawingtool.model {
	import com.zedia.drawingtool.events.PathEvent;
	import com.zedia.drawingtool.events.PointEvent;
	import com.zedia.drawingtool.events.PathVectorEvent;
	import com.zedia.drawingtool.model.objects.PathObject;
	import com.zedia.drawingtool.model.objects.PointObject;
 
	import org.robotlegs.mvcs.Actor;
 
	import flash.geom.Point;
 
	/**
	 * @author dominicg
	 */
	public class DrawingModel extends Actor {
		private var _pointVector:Vector.<PointObject>;
		private var _pathVector:Vector.<PathObject>;
		public function DrawingModel() {
			_pointVector = new Vector.<PointObject>();
			_pathVector = new Vector.<PathObject>();
		}
 
		public function addPoint(point:PointObject):void{
			point.id = _pointVector.length;
			_pointVector.push(point);
			dispatch(new PointEvent(PointEvent.ADD_POINT_APPROVED, point));
			var pointLength : int = _pointVector.length;
			if (_pointVector.length > 1) {
				var controlPoint:Point = new Point((_pointVector[pointLength - 1].x - _pointVector[pointLength - 2].x)/2, (_pointVector[pointLength - 1].y- _pointVector[pointLength - 2].y)/2);
				_pathVector.push(new PathObject(_pointVector[pointLength - 2], _pointVector[pointLength - 1], _pathVector.length, controlPoint));
				dispatch(new PathEvent(PathEvent.ADD_PATH_APPROVED, _pathVector[_pathVector.length -1]));
			}
		}
 
		public function updatePoint(point : PointObject) : void {
			trace (point.id);
			_pointVector[point.id].x = point.x;
			_pointVector[point.id].y = point.y;
			//Update paths now
			var resultingPathVector:Vector.<PathObject> = new Vector.<PathObject>();
			if (point.id == 0) {
				_pathVector[point.id].firstPoint = point;
				resultingPathVector.push(_pathVector[point.id]);
			} else if (point.id == _pointVector.length - 1){				
				_pathVector[point.id - 1].secondPoint = point;
				resultingPathVector.push(_pathVector[point.id - 1]);				
			} else {
				_pathVector[point.id].firstPoint = point;
				resultingPathVector.push(_pathVector[point.id]);
 
				_pathVector[point.id - 1].secondPoint = point;
				resultingPathVector.push(_pathVector[point.id - 1]);
			}
			dispatch(new PathVectorEvent(PathVectorEvent.UPDATE_PATHS, resultingPathVector));
		}
 
		public function updateControlPoint(path : PathObject) : void {
			_pathVector[path.id].controlPoint = path.controlPoint;
		}
	}
}

Well that is it for now. You can download the source code below and see the classes that I didn’t talk about. This is all good but this tool right now just draw paths but it doesn’t transform or export the data in any way. This will be the topic of a next post.

ZediaBezierTool

, , ,

  1. #1 by Will - June 17th, 2010 at 08:27

    Nice one. I’ve been using RobotLegs as my framework of choice for some time now. I think it’s great and you are correct about having to create a lot of classes but the benefit of a defined structure and loose coupling pays off.

    I created an Air app called SpriteMite which use RobotLegs and Signals exclusively. http://pixelartsoftware.com/

    Thanks for sharing.

(will not be published)
Subscribe to comments feed
  1. No trackbacks yet.