if(_global.spline == undefined) { _global.spline = {}; } spline.point = function() { this._x = this.x; this._y = this.y; this.segments = new Array(); }; spline.point.prototype = new MovieClip(); var point_prototype = spline.point.prototype; point_prototype.onPress = function() { this.onMouseMove = this.onDrag; } point_prototype.onRelease = function() { delete this.onMouseMove; this.reDraw(); } point_prototype.onDrag = function() { this._x = _root._xmouse; this._y = _root._ymouse; this.reDraw(); } point_prototype.reDraw = function(segment) { for(var s = 0; s < this.segments.length; s += 1) { this.segments[s].reDraw(); } } point_prototype.addSegment = function(segment) { this.segments.push(segment); trace('added a segment to '+this+' ('+this.segments.length+')'); } delete point_prototype; spline.anchor = function() { this._x = this.x; this._y = this.y; this.weight = this.w; this.segments = new Array(); this.controls = new Array(); this.label.text = this._name; this._alpha = 50; }; spline.anchor.prototype = new spline.point(); Object.registerClass("anchor", spline.anchor); var anchor_prototype = spline.anchor.prototype; anchor_prototype.onDrag = function() { var x_last = this._x; var y_last = this._y; this._x = _root._xmouse; this._y = _root._ymouse; for(var c = 0; c < this.controls.length; c += 1) { var cdiff_x = this.controls[c]._x - x_last; var cdiff_y = this.controls[c]._y - y_last; this.controls[c]._x = this._x + cdiff_x; this.controls[c]._y = this._y + cdiff_y; } this.reDraw(); } anchor_prototype.addControl = function(control) { this.controls.push(control); control.setAnchor(this); //trace('added a control to '+this+' ('+this.controls.length+')'); } delete anchor_prototype; spline.control = function() { this._x = this.x; this._y = this.y; this.segments = new Array(); this.anchor = undefined; this._alpha = 25; }; spline.control.prototype = new spline.point(); Object.registerClass("control", spline.control); var control_prototype = spline.control.prototype; control_prototype.onDrag = function() { this._x = _root._xmouse; this._y = _root._ymouse; if(this.anchor.controls.length == 2) { var radius = Math.sqrt(Math.pow((this.anchor._y - this._y), 2) + Math.pow((this.anchor._x - this._x), 2)); var theta = Math.atan((this._y - this.anchor._y) / (this._x - this.anchor._x)); if((this._x - this.anchor._x) <= 0) { theta += Math.PI; } // to account for discontinuity in tan/atan // desired angle for other control point theta += Math.PI; for(var c = 0; c < this.anchor.controls.length; c += 1) { var control = this.anchor.controls[c]; if(control != this) { // this R belongs to the "other" control point //var R = Math.sqrt(Math.pow((control._x - control.anchor._x), 2) + Math.pow((control._y - control.anchor._y), 2)); control._x = control.anchor._x + (radius * Math.cos(theta)); control._y = control.anchor._y + (radius * Math.sin(theta)); control.reDraw(); } } } this.reDraw(); } control_prototype.setAnchor = function(anchor) { this.anchor = anchor; } delete control_prototype; spline.segment = function(clip, anch_a, ctrl_a, ctrl_b, anch_b) { this.clip = clip; anch_a.addControl(ctrl_a); anch_b.addControl(ctrl_b); this.anchors = [anch_a, anch_b]; this.points = [anch_a, ctrl_a, ctrl_b, anch_b]; anch_a.addSegment(this); ctrl_a.addSegment(this); ctrl_b.addSegment(this); anch_b.addSegment(this); }; var segment_prototype = spline.segment.prototype; segment_prototype.reDraw = function() { this.clip.clear(); this.drawControlLines(); //this.drawMidLines(); this.drawSpline(); }; segment_prototype.midPoint = function(a, b) { return {_x: ((a._x + b._x) / 2), _y: ((a._y + b._y) / 2)} }; segment_prototype.drawLine = function(start, finish, weight, ink) { this.clip.lineStyle(weight, ink); this.clip.moveTo(start._x, start._y); this.clip.lineTo(finish._x, finish._y); }; segment_prototype.drawControlLines = function() { this.drawLine(this.points[0], this.points[1], 0, 0x333333); this.drawLine(this.points[3], this.points[2], 0, 0x333333); }; segment_prototype.drawMidLines = function() { // this line is drawn between the two control points this.drawLine(this.points[1], this.points[2], 0, 0x999999); // these are the midpoints between each pair of points var midA = this.midPoint(this.points[0], this.points[1]); var midB = this.midPoint(this.points[1], this.points[2]); var midC = this.midPoint(this.points[2], this.points[3]); this.drawLine(midA, midB, 0, 0x888888); this.drawLine(midB, midC, 0, 0x888888); // these are the midpoints between each pair of *midpoints* above var midAB = this.midPoint(midA, midB); var midBC = this.midPoint(midB, midC); this.drawLine(midAB, midBC, 0, 0x777777); }; segment_prototype.drawSpline = function() { // (a, b, c) defined at http://www.moshplant.com/direct-or/bezier/math.html var cx = 3 * (this.points[1]._x - this.points[0]._x); var bx = (3 * (this.points[2]._x - this.points[1]._x)) - cx; var ax = this.points[3]._x - this.points[0]._x - cx - bx; var cy = 3 * (this.points[1]._y - this.points[0]._y); var by = (3 * (this.points[2]._y - this.points[1]._y)) - cy; var ay = this.points[3]._y - this.points[0]._y - cy - by; //trace('----'); var t = 0.0; while(true) { if(t == 0.0) { // first iteration, so pretend we had a previouew iteration var x_last = this.points[0]._x; var y_last = this.points[0]._y; } // (xt, yt) is a point along the spline at time t var xt = (ax * Math.pow(t, 3)) + (bx * Math.pow(t, 2)) + (cx * t) + this.points[0]._x; var yt = (ay * Math.pow(t, 3)) + (by * Math.pow(t, 2)) + (cy * t) + this.points[0]._y; // midway between the current point and the last one var midpoint = this.midPoint({_x: x_last, _y: y_last}, {_x: xt, _y: yt}); // (xtd, ytd) is a vector showing the velocity of (xt, yt) var xtd = (3 * ax * Math.pow(t, 2)) + (2 * bx * t) + cx; var ytd = (3 * ay * Math.pow(t, 2)) + (2 * by * t) + cy; // this is just the rate of movement at t var rate = Math.sqrt(Math.pow(xtd, 2) + Math.pow(ytd, 2)); // and this is the direction of movement at time t var theta = Math.atan(ytd / xtd); if(xtd <= 0) { theta += Math.PI; } // to account for discontinuity in tan/atan // line weight at time t var weight = this.anchors[0].weight + (t * (this.anchors[1].weight - this.anchors[0].weight)); // tangent to one side var tangentA = theta - (Math.PI/2); var xtdt = Math.cos(tangentA); var ytdt = Math.sin(tangentA); var left_sidepoint = {_x: (xt + (weight * xtdt)), _y: (yt + (weight * ytdt))}; var left_sidepoint_edge = {_x: (xt + ((4 + weight) * xtdt)), _y: (yt + ((4 + weight) * ytdt))}; // tangent to the other side var tangentB = theta + (Math.PI/2); var xtdt = Math.cos(tangentB); var ytdt = Math.sin(tangentB); var right_sidepoint = {_x: (xt + (weight * xtdt)), _y: (yt + (weight * ytdt))}; var right_sidepoint_edge = {_x: (xt + ((4 + weight) * xtdt)), _y: (yt + ((4 + weight) * ytdt))}; delete xtdt; delete ytdt; // we have previous points to draw from if(midpoint_last && left_sidepoint_last && right_sidepoint_last) { this.clip.lineStyle(); this.clip.moveTo(left_sidepoint._x, left_sidepoint._y); // alternating colors to show internal details // if(even) { this.clip.beginFill(0xFFCC00); // } else { // this.clip.beginFill(0x666666); // } // draw the main trapezoid of this segment this.clip.lineTo(left_sidepoint_last._x, left_sidepoint_last._y); this.clip.lineTo(right_sidepoint_last._x, right_sidepoint_last._y); this.clip.lineTo(right_sidepoint._x, right_sidepoint._y); this.clip.lineTo(left_sidepoint._x, left_sidepoint._y); this.clip.endFill(); // draw the edges of ths segment, and maybe a center line // if(even) { //this.drawLine({_x: x_last, _y: y_last}, midpoint, 0, 0xFFFF00); this.drawLine(left_sidepoint_last, left_sidepoint, 0, 0xFFFF00); this.drawLine(right_sidepoint_last, right_sidepoint, 0, 0xFFFF00); // } else { //this.drawLine({_x: x_last, _y: y_last}, midpoint, 0, 0x000000); /* this.drawLine(left_sidepoint_last, left_sidepoint, 0, 0x000000); this.drawLine(right_sidepoint_last, right_sidepoint, 0, 0x000000); */ // } } if(even) { var even = false; } else { var even = true; } if(t >= 1.0) { // we're done-diggity-done break; } else { // increment t by an amount proportional to the rate of movement t = Math.min(1.0, (t + Math.min(0.05, Math.max(0.001, (rate / 10000))))); // previous values for the next iteration var midpoint_last = midpoint; var left_sidepoint_last = left_sidepoint; var left_sidepoint_edge_last = left_sidepoint_edge; var right_sidepoint_last = right_sidepoint; var right_sidepoint_edge_last = right_sidepoint_edge; var theta_last = theta; var x_last = xt; var y_last = yt; var xtd_last = xtd; var ytd_last = ytd; } } }; delete segment_prototype; spline.vector = function(clip, start, end) { this.x = (end.x - start.x); this.y = (end.y - start.y); this.clip = clip; }; spline.vector.prototype = new MovieClip(); var vector_prototype = spline.vector.prototype; vector_prototype.addToPoint = function(point) { var newVector = new spline.vector(this.clip, undefined, undefined); newVector.x = (point.x + this.x); newVector.y = (point.y + this.y); return newVector; }; vector_prototype.subtractFromPoint = function(point) { var newVector = new spline.vector(this.clip, undefined, undefined); newVector.x = (point.x - this.x); newVector.y = (point.y - this.y); return newVector; }; vector_prototype.scaleToLength = function(length) { var newVector = new spline.vector(this.clip, undefined, undefined); var currentLength = Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); newVector.x = (this.x * (length / currentLength)); newVector.y = (this.y * (length / currentLength)); return newVector; }; vector_prototype.draw = function(base) { this.clip.lineStyle(0, 0x000099); this.clip.moveTo(base.x, base.y); this.clip.lineTo((base.x + this.x), (base.y + this.y)); }; vector_prototype.drawBack = function(base) { this.clip.lineStyle(0, 0x660000); this.clip.moveTo(base.x, base.y); this.clip.lineTo((base.x - this.x), (base.y - this.y)); }; delete vector_prototype; _root.connect = function(anchors) { _root.clear(); for(var i = 0; i < (anchors.length - 1); i += 1) { var before = anchors[i - 1]; var start = anchors[i + 0]; var end = anchors[i + 1]; var after = anchors[i + 2]; var vectorSegment = new spline.vector(_root, start, end); if(before == undefined) { if(after != undefined) { // current starting point is the beginning of the line var vectorStart = new spline.vector(_root, after, end); // not sure why this is reversed } } else { // we're somewhere in the middle of the line var vectorStart = new spline.vector(_root, before, end); } if(after == undefined) { if(before != undefined) { // current ending point is the end of the line var vectorEnd = new spline.vector(_root, start, before); // not sure why this is reversed } } else { // we're somewhere in the middle of the line var vectorEnd = new spline.vector(_root, start, after); } if((vectorStart != undefined) && (vectorEnd != undefined)) { var segmentLength = Math.sqrt(Math.pow(vectorSegment.x, 2) + Math.pow(vectorSegment.y, 2)); vectorStart = vectorStart.scaleToLength(segmentLength / 2); vectorEnd = vectorEnd.scaleToLength(segmentLength / 2); /* vectorStart.draw(start); vectorEnd.drawBack(end); */ var controlName = 'control-'+start._name+'-'+end.name; _root.attachMovie('control', controlName+'-start', (300 + i), vectorStart.addToPoint(start)); _root.attachMovie('control', controlName+'-end', (400 + i), vectorEnd.subtractFromPoint(end)); var segmentName = 'segment-'+start._name+'-'+end.name; _root.createEmptyMovieClip(segmentName, (100 + i)); var segment = new spline.segment(_root[segmentName], start, _root[controlName+'-start'], _root[controlName+'-end'], end); segment.reDraw(); } } }; // drop some points _root.attachMovie('anchor', 'anch-A', 201, {x: (100 + (Math.random() * 200)), y: (100 + (Math.random() * 200)), w: 2}); _root.attachMovie('anchor', 'anch-B', 202, {x: (100 + (Math.random() * 200)), y: (100 + (Math.random() * 200)), w: 4}); _root.attachMovie('anchor', 'anch-C', 203, {x: (100 + (Math.random() * 200)), y: (100 + (Math.random() * 200)), w: 8}); _root.attachMovie('anchor', 'anch-D', 204, {x: (100 + (Math.random() * 200)), y: (100 + (Math.random() * 200)), w: 12}); _root.attachMovie('anchor', 'anch-E', 205, {x: (100 + (Math.random() * 200)), y: (100 + (Math.random() * 200)), w: 16}); _root.attachMovie('anchor', 'anch-F', 206, {x: (100 + (Math.random() * 200)), y: (100 + (Math.random() * 200)), w: 20}); var anchors = new Array(_root['anch-A'], _root['anch-B'], _root['anch-C'], _root['anch-D'], _root['anch-E'], _root['anch-F']); _root.connect(anchors);