AS3 spirograph with source code

I just worked a bit more on my AS3 spirograph tonight. This is the updated version with the full source code:

Demo:

Full source code:

Please note that you need the Hype framework and Minimalcomps to compile this.

 package {
	import hype.extended.util.ContextSavePNG;
	import hype.framework.display.BitmapCanvas;
 
	import com.bit101.components.CheckBox;
	import com.bit101.components.ColorChooser;
	import com.bit101.components.Label;
	import com.bit101.components.PushButton;
	import com.bit101.components.Slider;
 
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
 
	/**
	 * @author Jankees van Woezik
	 */
	public class Spirograph extends Sprite {
		private var t : Number = 0;
		private var _canvas : BitmapCanvas;
		private var _original : Sprite;
		private var _isVirgin : Boolean = true;
		private var _gearsVisual : Sprite;
		// minimalcomps.com
		private var _speedSlider : Slider;
		private var _outerGearSlider : Slider;
		private var _innerGearSlider : Slider;
		private var _offsetPenSlider : Slider;
		private var _penPointShape : Ball;
		private var _drawAnimatedCheckbox : CheckBox;
		private var _showWheelsCheckbox : CheckBox;
		private var _colorPicker : ColorChooser;
 
		public function Spirograph() {
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
 
			stage.addEventListener(Event.RESIZE, handleStageResize);
 
			// gradient background (thanks to zwartekoffie.com)
			addChild(new SiteBackground());
 
			_original = new Sprite();
 
			_canvas = new BitmapCanvas(stage.stageWidth, stage.stageHeight, true);
			_canvas.target = _original;
			addChild(_canvas);
 
			_gearsVisual = new Sprite();
			addChild(_gearsVisual);
 
			var controlY : Number = 10;
 
			var randomize : PushButton = new PushButton(this, 10, controlY, "Randomize settings", randomSettings);
			randomize.width = 172;
 
			new Label(this, 10, controlY += 30, "speed");
			_speedSlider = new Slider(Slider.HORIZONTAL, this, 80, controlY + 4);
			_speedSlider.minimum = 1;
			_speedSlider.value = 10;
			_speedSlider.maximum = 20;
 
			new Label(this, 10, controlY += 15, "outer gear (R)");
			_outerGearSlider = new Slider(Slider.HORIZONTAL, this, 80, controlY + 4, change);
			_outerGearSlider.minimum = 1;
			_outerGearSlider.value = 100;
			_outerGearSlider.maximum = 200;
 
			new Label(this, 10, controlY += 15, "inner gear (r)");
			_innerGearSlider = new Slider(Slider.HORIZONTAL, this, 80, controlY + 4, change);
			_innerGearSlider.minimum = 1;
			_innerGearSlider.value = 50;
			_innerGearSlider.maximum = 200;
 
			new Label(this, 10, controlY += 15, "offset pen (p)");
			_offsetPenSlider = new Slider(Slider.HORIZONTAL, this, 80, controlY + 4, change);
			_offsetPenSlider.minimum = 1;
			_offsetPenSlider.value = 6;
			_offsetPenSlider.maximum = 200;
 
			new Label(this, 10, controlY += 19, "line color");
			_colorPicker = new ColorChooser(this, 80, controlY, 0x59c7b7);
 
			_drawAnimatedCheckbox = new CheckBox(this, 12, controlY += 30, "show animation", toggleAnimated);
			_drawAnimatedCheckbox.selected = true;
 
			_showWheelsCheckbox = new CheckBox(this, 12, controlY += 15, "show gears");
 
			var explanation : Label = new Label(this, 10, controlY += 20, "");
			explanation.text = "Formula\nx(t)=(R-r)*cos(t) + p*cos((R-r)*t/r) \ny(t)=(R-r)*sin(t) - p*sin((R-r)*t/r)";
 
			new ContextSavePNG(_canvas, stage);
 
			_penPointShape = new Ball(_colorPicker.value);
			addChild(_penPointShape);
 
			addEventListener(Event.ENTER_FRAME, handleEnterFrame);
 
			randomSettings();
		}
 
		private function handleEnterFrame(event : Event) : void {
			if (_drawAnimatedCheckbox.selected) renderTick();
		}
 
		private function handleStageResize(event : Event) : void {
			removeChild(_canvas);
			_canvas.clear();
			_canvas = null;
			_canvas = new BitmapCanvas(stage.stageWidth, stage.stageHeight, true, 0xffffff);
			_canvas.target = _original;
			addChildAt(_canvas, 1);
			clearCanvas();
		}
 
		private function toggleAnimated(e : Event) : void {
			clearCanvas();
			if (!_drawAnimatedCheckbox.selected) drawStatic();
		}
 
		private function change(e : Event) : void {
			clearCanvas();
			if (_drawAnimatedCheckbox) if (!_drawAnimatedCheckbox.selected) drawStatic();
		}
 
		private function drawStatic() : void {
			var leni : uint = 6000;
			for (var i : uint = 0; i < leni; i++) {
				renderTick();
			}
		}
 
		private function clearCanvas() : void {
			_canvas.clear();
			_isVirgin = true;
		}
 
		private function randomSettings(e : Event = null) : void {
			_outerGearSlider.value = Random.between(_outerGearSlider.maximum - 50, _outerGearSlider.maximum);
			_innerGearSlider.value = Random.between(_innerGearSlider.minimum, _outerGearSlider.value);
			_offsetPenSlider.value = Random.between(_offsetPenSlider.minimum, _innerGearSlider.value);
		}
 
		private function renderTick() : void {
			t += (_speedSlider.value / 100);
 
			if (_offsetPenSlider.value > _innerGearSlider.value) {
				_offsetPenSlider.value = _innerGearSlider.value;
			}
			if (_innerGearSlider.value > _outerGearSlider.value) {
				_innerGearSlider.value = _outerGearSlider.value;
			}
 
			_original.graphics.lineStyle(1, _colorPicker.value, 1);
			_original.graphics.moveTo(_penPointShape.x, _penPointShape.y);
 
			var a : Number = _outerGearSlider.value;
			var b : Number = _innerGearSlider.value;
			var o : Number = _offsetPenSlider.value;
 
			var newx : Number;
			var newy : Number;
 
			// http://en.wikipedia.org/wiki/Spirograph
 
			// as found on http://linuxgazette.net/133/luana.html
			//
			// x(t)=(R-r)*cos(t) + p*cos((R-r)*t/r)
			// y(t)=(R-r)*sin(t) - p*sin((R-r)*t/r)
			//
			// R, the radius of the fixed circle;
			// r, the radius of the moving circle;
			// p, the distance from the pen to the moving circle center.
			// O, The center of the fixed circle
 
			newx = (a - b) * Math.cos(t) + o * Math.cos((a - b) * t / b);
			newy = (a - b) * Math.sin(t) + o * Math.sin((a - b) * t / b);
 
			_penPointShape.x = newx + stage.stageWidth / 2;
			_penPointShape.y = newy + stage.stageHeight / 2;
 
			_penPointShape.color = _colorPicker.value;
 
			if (!_isVirgin) {
				_original.graphics.lineTo(_penPointShape.x, _penPointShape.y);
			}
 
			_gearsVisual.graphics.clear();
			if (_showWheelsCheckbox.selected) {
				var centerInnerCircleX : Number = stage.stageWidth / 2 + (Math.cos(t) * (a - b));
				var centerInnerCircleY : Number = stage.stageHeight / 2 + (Math.sin(t) * (a - b));
 
				_gearsVisual.graphics.beginFill(0xfffe8e, 0.3);
				_gearsVisual.graphics.drawCircle(stage.stageWidth / 2, stage.stageHeight / 2, a);
				_gearsVisual.graphics.beginFill(0xfe6cc7, 0.3);
				_gearsVisual.graphics.drawCircle(centerInnerCircleX, centerInnerCircleY, b);
				_gearsVisual.graphics.beginFill(0xfe6cc7, 0.3);
				_gearsVisual.graphics.drawCircle(centerInnerCircleX, centerInnerCircleY, 3);
				_gearsVisual.graphics.endFill();
			}
 
			_canvas.capture(true);
 
			_original.graphics.clear();
 
			_isVirgin = false;
		}
	}
}
import nl.inlet42.utils.view.StageProvider;
 
import com.epologee.util.ColorUtils;
 
import flash.display.GradientType;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Matrix;
 
class Ball extends Sprite {
	private var _color : uint;
 
	public function Ball(inColor : uint) {
		color = inColor;
	}
 
	public function set color(color : uint) : void {
		if (_color == color) return;
		_color = color;
		with(graphics) {
			clear();
			beginFill(_color, 1);
			drawCircle(0, 0, 3);
			endFill();
		}
	}
}
class SiteBackground extends Sprite {
	public function SiteBackground() {
		if (stage) {
			initUI(null);
		} else {
			addEventListener(Event.ADDED_TO_STAGE, initUI, false, 0, true);
		}
	}
 
	private function initUI(event : Event) : void {
		removeEventListener(Event.ADDED_TO_STAGE, initUI);
		stage.addEventListener(Event.RESIZE, handleResize);
		handleResize();
	}
 
	private function handleResize(event : Event = null) : void {
		graphics.clear();
 
		var colors : Array = [0xfdfdfb, 0xeae7e0];
		var alphas : Array = [1, 1];
		var ratios : Array = [0, 255];
		var matrix : Matrix = new Matrix();
		matrix.createGradientBox(stage.stageWidth, stage.stageWidth, stage.stageWidth / 2 - stage.stageWidth / 2, stage.stageHeight / 2 - stage.stageWidth / 2);
		graphics.beginGradientFill(GradientType.RADIAL, colors, alphas, ratios, matrix);
		graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
	}
}
class Random {
	public static function between(inFrom : Number, inTo : Number, inFloat : Boolean = false) : Number {
		var from : Number = (inFrom < inTo) ? inFrom : inTo;
		var to : Number = (inTo > inFrom) ? inTo : inFrom;
		return inFloat ? (from + Math.random() * Math.abs(to - from)) : (from + Math.round(Math.random() * Math.abs(to - from)));
	}
}

THE COMMENTS:



  • What Patrick Pietens said 13 hours later:

    Why not jump in the HTML5 bandwagon and use the canvas object to draw the spirograph?

  • What jankees said 13 hours later:

    I was actually already working on that… :-)

Leave a Reply