Tutorials, extensions, and source files for ActionScript, Flash, and other Adobe products.

 

senocular.com ActionScript Library

Path.as

Name: Path (motion guide) Class: define a path for movement/orientation 
Author: senocular: www.senocular.com
Date: 1899-12-31T00:38:14.300
Documentation:
Class PATH: a class for defining motion paths similar
to Flash motion guides.  You can draw or move
along these paths after defined. (see exeamples)

myPath = new Path(start_x, start_y);
- defines a new Path object and optionally places the path start at start_x, start_y (basically
combining a moveTo with the constructor call).

Arguments:
- start_x: starting x location of the path.
- start_y: starting y location of the path.

Returns:
- a new Path object.

Path objects have the following properties:
_length
_position
curveToAccuracy
and the following methods:
moveTo();
lineTo();
curveTo();
circleCWTo();
circleCCWTo();
clear();
reverse();
traverse();
draw();
drawUpTo();

[Properties]
myPath._length
- (read only) the length of the path.  If the path is just a line from point 0,0 to point 100,0
the _length would be 100.

myPath._position
- (read only) current position of the Path objects drawing. It exists as an object with
properties _x and _y both relating to the x and y coordinates of that point.  To change
this, use moveTo();

myPath.curveToAccuracy
- determines the number of lines to use in evaluating a curveTo quadratic bezier curve.  To
allow for smooth movement along the path, its divided into seperate straight line segments.
This value dictates how many segments for each curveTo.  The more segments the more accuracy
though more segments also means a drop in performance.  Default: 10.

[Methods]
moveTo(start_x, start_y);
- moves the current _position of the Path drawing to the passed coordinates. The next line or
curve added will start from this point.
Arguments:
- start_x: new starting x location of the path.
- start_y: new starting y location of the path.
Returns:
- the Path object

lineTo(end_x, end_y);
- extends and adds to the path a line from the current _position to the passed coordinates.
The line created from this extension will be part of the path and will be traveled along in
a path.traverse().  This operates much like MovieClip.lineTo();
Arguments:
- end_x: the ending x position of the line.
- end_y: the ending y position of the line.
Returns:
- the Path object

curveTo(con_x, con_y, end_x, end_y, type);
- extends and adds to the path a curve from the current _position to the passed coordinates.
The curve created from this extension will be part of the path and will be traveled along in
a path.traverse().  This operates much like MovieClip.curveTo();
Note: curveTo's are approximated in paths using straight line segments.  You can adjust the
accuracy of this approximation using myPath.curveToAccuracy;
Arguments:
- con_x: the x position of the control point.
- con_y: the y position of the control point.
- end_x: the ending x position of the curve.
- end_y: the ending y position of the curve.
Returns:
- the Path object

circleTo(end_x, end_y, arc [, direction]);
circleCWTo(end_x, end_y, arc);
circleCCWTo(end_x, end_y, arc);
- extends and adds to the path a circular curve from the current _position to the passed end
coordinates. 
Arguments:
- end_x: the ending x position of the curve.
- end_y: the ending y position of the curve.
- arc: the percentage of a circle to use in the curve.  .5 or 1/2 is a half-circle making
a half-circle arc from the current _position to the end_x and end_y.  .25 would be a quarter
of a circle.  This value needs to be between 0 and 1 (non inclusive) - default: .5
- direction: (string, circleTo only, optional) determines whether the curve created is to
move clockwise ("CW") or counter-clockwise ("CCW"). Default: "CW" or counter-clockwise.
circleCWTo and circleCWTo are just circleTo with this value already applied. It is
recomended that these are used in favor of just circleTo for clearity.
Returns:
- the Path object

traverse(object, t, orient, wrap);
- places passed object's _x and _y at 't' location on the path.
Arguments:
- object: an object, most commonly a movieclip, to be placed on the path.
- t: (float) a number between 0 and 1 which relates to be a point along the path where
0 is the start _position of the path and 1 is the end position.
- orient: (optional, boolean) a true or false value which indicates whether or not the
object will have its _rotation to match the path's _rotation. Default: false, no _rotation.
- wrap: (optional) determines whether a t value outside the range of 0 through 1
is either capped off at 0 or 1 (false) or wraps. In other words a wrapping t value will
convert a 1.5 to .5.  Default: true.
Returns:
- the Path object

Note:
- for a specific speed (in pixels per unit of movement), you can increment t by
speed/myPath._length;

Path.draw(mc)
- draws the path using Flash drawing API
Arguments:
- mc: (movieClip) the movieclip the path is to be drawn in.
Returns:
- the Path object.

Path.clear()
- clears the Path object of all paths
Arguments:
- none
Returns:
- nothing

myPath.drawUpTo(mc, t, wrap)
- draws a path upto a certain t value in a movieclip mc.
Arguments:
- mc: the movieclip in which the path is to be drawn
- t: the t position along the path to be drawn to
- wrap: (optional) determines whether a t value outside the range of 0 through 1
is either capped off at 0 or 1 (false) or wraps. In other words a wrapping t value will
convert a 1.5 to .5.  Default: true.
Returns:
- the path object

myPath.reverse()
- creates a new Path object thats the reverse of the current
Technically, this is only useful in combination with drawUpTo since you can traverse a path
backwards by changing the direction of t in the traverse call (or 1-t);
Arguments:
- none
Returns:
- a new Path object thats the reverse path of the Path reverse was used on.

Example:
// This creats a half circle path, myPath
// which myMovieClip follows and completes
// in 100 frames (1/.01 = 100).
myPath = new Path(0,0);
myPath.lineTo(100,0);
myPath.circleTo(50,0, .5);

t = 0;
myMovieClip.onEnterFrame = function(){
	t += .01;
	if (t>1) t=0;
	myPath.traverse(this, t);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
Path = function(){
	this.init.apply(this,arguments);
};
Path.prototype.init = function(start_x, start_y){
	this._segments = [];
	this._length = 0;
	if (arguments.length) this.moveTo(start_x, start_y);
	else this.moveTo(0,0);
};
Path.prototype.curveToAccuracy = 10;
Path.prototype.clear = function(){
	this.init(0,0);
};
Path.prototype.moveTo = function(start_x, start_y){
	this._segments[this._segments.length] = {d:0, start:{_x:this._position._x, _y:this._position._y},  end:{_x: start_x, _y:start_y}, type:"M"};
	this._position = {_x:start_x, _y:start_y};
	return this;
};
Path.prototype.lineTo = function(end_x, end_y){
	var dx = end_x-this._position._x;
	var dy = end_y-this._position._y;
	var d = Math.sqrt(dx*dx+dy*dy);
	this._segments[this._segments.length] = {d:d, start:this._position, end:{_x:end_x, _y:end_y}, type:"L"};
	this._length += d;
	this._position = {_x:end_x, _y:end_y};
	return this;
}
Path.prototype.circleTo = Path.prototype.circleCWTo = function(end_x, end_y, arc, dir){
	if (dir == undefined) dir = "CW";
	if (arc == undefined) arc = .5;
	if (arc > 1) arc = 1;
	else if (arc <= 0) return this.lineTo(end_x, end_y);
	if (dir == "CCW") arc = 1-arc;
	var o = {_x:(this._position._x+end_x)/2, _y:(this._position._y+end_y)/2};
	var dx = end_x-this._position._x, dy = end_y-this._position._y;
	var d = Math.sqrt(dx*dx+dy*dy);
	var mr = d/2;
	var opp = mr/Math.tan(arc*Math.PI);
	var rad =  mr/Math.sin(arc*Math.PI);
	var a = Math.atan2(dy,dx) + Math.PI/2;
	o._x += Math.cos(a)*opp;
	o._y += Math.sin(a)*opp;
	var a1 = Math.atan2(this._position._y-o._y, this._position._x-o._x);
	var a2 = Math.atan2(end_y-o._y, end_x-o._x);
	if (dir == "CW"){ if (a2 < a1) a2 += Math.PI*2;
	}else{ if (a1 < a2) a1 += Math.PI*2; }
	var d = rad*Math.abs(a2-a1);
	this._segments[this._segments.length] = {d:d, r:rad, a1:a1, a2:a2, o:o, dir:dir, type:"C"};
	this._length += d;
	this._position = {_x:end_x, _y:end_y};
	return this;
};
Path.prototype.circleCCWTo = function(end_x, end_y, arc){
	return this.circleTo.call(this, end_x, end_y, arc,  "CCW");
};
Path.prototype.curveTo = function(con_x, con_y, end_x, end_y){
	var segs = this.divideBezier(this._position._x, this._position._y, con_x, con_y, end_x, end_y);
	this._segments[this._segments.length] = {d:segs.d, start:this._position, con:{_x:con_x, _y:con_y}, end:{_x:end_x, _y:end_y}, segs:segs, type:"B"};
	this._length += segs.d;
	this._position = {_x:end_x, _y:end_y};
	return this;
};
Path.prototype.divideBezier = function(x1,y1,x2,y2,x3,y3){
	var t,ax,ay,dx,dy,sx,sy,x,y,d,s=[],p={_x:x1,_y:y1};
	var dx1=x2-x1,dy1=y2-y1,dx2=x3-x2,dy2=y3-y2;
	var MA=Math.atan2,MS=Math.sqrt,td=0,ad,da,a1,a=MA(dy1,dx1);
	for (var i=1; i<this.curveToAccuracy; ++i){
		t = i/this.curveToAccuracy;
		dx = x2+dx2*t - (ax = x1+dx1*t);
		dy = y2+dy2*t - (ay = y1+dy1*t);
		x  = ax+dx*t;	y  = ay+dy*t;
		sx = x-p._x;	sy = y-p._y;
		td += (d = MS(sx*sx+sy*sy));
		a1=MA(dy,dx);
		da = a1-a;
		if (da > Math.PI) da -= Math.PI*2;
		else if (da < -Math.PI) da += Math.PI*2;
		s[s.length] = {d:d, start:p, end:{_x:x ,_y:y}, a:{base:a, d:da}};
		a=a1;
		p={_x:x ,_y:y};
	};
	sx = x3-p._x;	sy = y3-p._y;
	td += (d = MS(sx*sx+sy*sy));
	a1=MA(dy2,dx2);
	s[s.length] = {d:d, start:p, end:{_x:x3 ,_y:y3}, a:{base:a, d:a1-a}};
	s.d = td;
	return s;
};
Path.prototype.setInBezier = function(obj, t, segments, orient, extra){
	if (t<0){ t = 0 }else if (t>1){ t=1; }
	var seg=segments[0], currt = segments.d*t, currd=0, num=0, L=segments.length+1;
	for (var i=1; i<L; ++i){
		seg = segments[(num = i-1)];
		if (i == L || (currd + seg.d) >= currt) break;
		currd += seg.d;
	};
	if (!seg.d) t = 0;
	else t = (currt-currd)/seg.d;
	var dx = seg.end._x - seg.start._x;
	var dy = seg.end._y - seg.start._y;
	obj._x = seg.start._x + dx*t;
	obj._y = seg.start._y + dy*t;
	if (extra){
		obj.t = t; obj.n = num;
		obj.divs = segments.length;
	};
	if (orient) obj._rotation = (seg.a.base+seg.a.d*t)*180/Math.PI;
};
Path.prototype.reverse = function(){
	var s = this._segments, i=s.length;
	var p = new Path(s[i-1].end._x, s[i-1].end._y);
	while(i--){
		if (s[i].type == "M") p._segments[p._segments.length] = {d:0, start:{_x:s[i].end._x, _y:s[i].end._y}, end:{_x:s[i].start._x, _y:s[i].start._y}, type:"M"};
		else if (s[i].type == "L") p._segments[p._segments.length] = {d:s[i].d, start:{_x:s[i].end._x, _y:s[i].end._y}, end:{_x:s[i].start._x, _y:s[i].start._y}, type:"L"};
		else if (s[i].type == "C")p._segments[p._segments.length] = {d:s[i].d, r:s[i].r, a1:s[i].a2, a2:s[i].a1, o:{_x:s[i].o._x, _y:s[i].o._y}, dir:(s[i].dir == "CW" ? "CCW" : "CW"), type:"C"};
		else if (s[i].type == "B"){
			var segs = this.divideBezier(s[i].end._x, s[i].end._y, s[i].con._x, s[i].con._y, s[i].start._x, s[i].start._y);
			p._segments[p._segments.length] = {d:s[i].d, start:{_x:s[i].end._x, _y:s[i].end._y}, con:{_x:s[i].con._x, _y:s[i].con._y}, end:{_x:s[i].start._x, _y:s[i].start._y}, segs:segs, type:"B"};
		}
	}
	p._length = this._length;
	if (this.hasOwnProperty("curveToAccuracy")) p.curveToAccuracy = this.curveToAccuracy;
	return p;
};
Path.prototype.traverse = function(obj, t, orient, wrap){
	if (wrap == undefined || wrap == true) if (t<0 || t>1) t-=Math.floor(t);
	else{ if (t<0){ t = 0 }else if (t>1){ t=1; }}
	var seg=this._segments[0], currt=this._length*t, currd=0, num=0, L=this._segments.length+1;
	for (var i=1; i<L; ++i){
		seg = this._segments[(num = i-1)];
		if (i == L || (currd + seg.d) >= currt) break;
		else currd += seg.d;
	}
	while(seg.type == "M") seg = this._segments[++num];
	if (!seg.d) t = 0;
	else t = (currt-currd)/seg.d;
	if (seg.type == "L"){ // line
		var dx = seg.end._x - seg.start._x;
		var dy = seg.end._y - seg.start._y;
		obj._x = seg.start._x + dx*t;
		obj._y = seg.start._y + dy*t;
		if (orient) obj._rotation = Math.atan2(dy, dx)*180/Math.PI;
	}else if (seg.type == "C"){
		var a1 = seg.a1, a2 = seg.a2;
		var a = a1 + (a2-a1)*t;
		obj._x = seg.o._x + Math.cos(a)*seg.r;
		obj._y = seg.o._y + Math.sin(a)*seg.r;
		if (orient) obj._rotation = (seg.dir == "CW") ? a*180/Math.PI + 90 : a*180/Math.PI - 90;
	}else if (seg.type == "B") this.setInBezier(obj, t, seg.segs, orient);
	return this;
};
Path.prototype.mcCircleTo = function(cen_x, cen_y, a1, a2, r, dir) {
	dir = (dir == "CCW") ? -1 : 1;
	var MC = Math.cos, MS = Math.sin;
	var diff = Math.abs(a2-a1);
	var divs = Math.floor(diff/(Math.PI/4))+1;
	var span = dir * diff/(2*divs);
	var rc = r/MC(span);
	for (var i=0; i<divs; ++i) {
		a2 = a1+span; a1 = a2+span;
		this.curveTo(cen_x+MC(a2)*rc, cen_y+MS(a2)*rc, cen_x+MC(a1)*r, cen_y+MS(a1)*r);
	};
	return this;
};
Path.prototype.draw = function(mc, n){
	var s = this._segments;
	if (n == undefined) n = s.length;
	for (var i=0; i<n; ++i){
		if (s[i].type == "M") mc.moveTo(s[i].end._x, s[i].end._y);
		else if (s[i].type == "L") mc.lineTo(s[i].end._x, s[i].end._y);
		else if (s[i].type == "C") this.mcCircleTo.call(mc,s[i].o._x, s[i].o._y, s[i].a1, s[i].a2, s[i].r, s[i].dir);
		else if (s[i].type == "B") mc.curveTo(s[i].con._x, s[i].con._y, s[i].end._x, s[i].end._y);
	};
	return this;
};
Path.prototype.drawUpTo = function(mc, t, wrap){
	if (wrap == undefined || wrap == true) if (t<0 || t>1) t-=Math.floor(t);
	else{ if (t<0){ t = 0 }else if (t>1){ t=1; }}
	var seg=this._segments[0], currt=this._length*t, currd=0, num=0, L=this._segments.length+1;
	for (var i=1; i<L; ++i){
		seg = this._segments[(num = i-1)];
		if (i == L || (currd + seg.d) >= currt) break;
		else currd += seg.d;
	}
	if (!seg.d) t = 0;
	else t = (currt-currd)/seg.d;
	this.draw(mc, num);
	if (seg.type == "M") mc.moveTo(seg.end._x, seg.end._y);
	else if (seg.type == "L") mc.lineTo(seg.start._x + (seg.end._x-seg.start._x)*t, seg.start._y + (seg.end._y-seg.start._y)*t);
	else if (seg.type == "C") this.mcCircleTo.call(mc, seg.o._x, seg.o._y, seg.a1, seg.a1+(seg.a2-seg.a1)*t, seg.r, seg.dir);
	else if (seg.type == "B"){
		var obj = {};
		this.setInBezier(obj, t, seg.segs, false, true);
		var p = (obj.n/obj.divs) + (obj.t/obj.divs);
		mc.curveTo(seg.start._x+(seg.con._x-seg.start._x)*p, seg.start._y+(seg.con._y-seg.start._y)*p, obj._x, obj._y);
	}
	return this;
};