Spirography

My wife mentioned something about Spirographs yesterday, so I thought “hey – why not make one in actionscript”. Two seconds of googling found the formulas I was looking for (as well as a nifty java example), and about 10 minutes of coding had something up and running in Flash. Play around with the sliders and hit “draw” to create a little Spirograph masterpiece of your own.

Speaking of controls, the first slider controls the radius of the fixed circle, the second slider controls the radius of the rotating circle, the third slider controls the offset of the “pen tip” in the rotating circle, the checkbox specifies whether you’re drawing outside of the fixed circle or not, the color swatch will let you select a color to draw in, and finally, if you just want to see the thing in action, click on the “Randomize!” button and see what you get.

Get Adobe Flash player

If interested, the whole script is below (I used bit-101′s minimal slider, button, and checkbox, and my own color picker, if you’d like to compile this thing):

package {
 
	import com.bit101.components.CheckBox;
	import com.bit101.components.ColorChooser;
	import com.bit101.components.PushButton;
	import com.bit101.components.Slider;
 
	import com.onebyonedesign.ui.events.ColorEvent;
	import com.onebyonedesign.ui.OBO_ColorPicker;
 
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
 
	import flash.events.Event;
	import flash.events.MouseEvent;
 
	/**
	 * Spirograph (based on java implementation by Anu Garg http://wordsmith.org/anu/java/spirograph.html)
	 * @author Devon O.
	 */
 
	[SWF(width='550', height='450', backgroundColor='#000000', frameRate='60')]
	public class Main extends Sprite {
 
		private var _outside:Boolean = false;
 
		private var _data:BitmapData;
		private var _spiro:Sprite;
 
		private var _xoffset:Number;
		private var _yoffset:Number;
 
		private var _color:uint = 0xFFFF00FF;
 
		private var R:int = 1;
		private var r:int = 1;
		private var O:int = 1;
 
		private var largeRadiusSlider:Slider;
		private var smallRadiusSlider:Slider;
		private var cOffsetSlider:Slider;
 
		public function Main():void {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
 
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
 
			_xoffset = stage.stageWidth * .5;
			_yoffset = stage.stageHeight * .5;
 
			_data = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0x00000000);
			var bmp:Bitmap = new Bitmap(_data);
			bmp.x -= bmp.width * .5;
			bmp.y -= bmp.height * .5;
 
			_spiro = new Sprite();
			_spiro.addChild(bmp);
			_spiro.x = _xoffset;
			_spiro.y = _yoffset;
			addChild(_spiro);
 
			initUI();
 
			addEventListener(Event.ENTER_FRAME, frameHandler);
		}
 
		private function initUI():void {
			largeRadiusSlider = new Slider("horizontal", this, 440, 10, setLargeRadius);
			largeRadiusSlider.minimum = 1;
			largeRadiusSlider.maximum = 100;
 
			smallRadiusSlider = new Slider("horizontal", this, 440, 25, setSmallRadius);
			smallRadiusSlider.minimum = 1;
			smallRadiusSlider.maximum = 100;
 
			cOffsetSlider = new Slider("horizontal", this, 440, 40, setCircleOffset);
			cOffsetSlider.minimum = 1;
			cOffsetSlider.maximum = 100;
 
			var outsideCB:CheckBox = new CheckBox(this, 440, 55, "Draw outside", outsideHandler);
 
			var colorSelector:OBO_ColorPicker = new OBO_ColorPicker(20, 20, 0xFF00FF);
			colorSelector.x = 440;
			colorSelector.y = 70;
			colorSelector.addEventListener(ColorEvent.COLOR_SELECT, onColor);
			addChild(colorSelector);
 
			var drawButton:PushButton = new PushButton(this, 440, 95, "Draw", drawDesign);
 
			var randButton:PushButton = new PushButton(this, 440, 120, "Randomize!", mixItup);
		}
 
		private function setLargeRadius(event:Event):void {
			R = Math.round(event.currentTarget.value); 
		}
 
		private function setSmallRadius(event:Event):void {
			r = Math.round(event.currentTarget.value); 
		}
 
		private function setCircleOffset(event:Event):void {
			O = Math.round(event.currentTarget.value); 
		}
 
		private function outsideHandler(event:MouseEvent):void {
			_outside = event.currentTarget.selected;
		}
 
		private function onColor(event:ColorEvent):void {
			setColor(event.color);
		}
 
		private function mixItup(event:MouseEvent):void {
			R = randRange(100, 1);
			largeRadiusSlider.value = R;
 
			r = randRange(100, 1);
			smallRadiusSlider.value = r;
 
			O = randRange(100, 0);
			cOffsetSlider.value = O;
 
			drawDesign(null);
		}
 
		/**
		 * change 24 bit color to 32 bit
		 */
		private function setColor(color:uint):void {
			var a:uint = 0xFF;
			var r:uint = color >> 16;
			var g:uint = color >> 8 & 0xFF;
			var b:uint = color & 0xFF;
 
			_color = a << 24 | r << 16 | g << 8 | b;
		}
 
		private function drawDesign(event:MouseEvent):void {
 
			/*
				Where:
				R = radius of fixed circle
				r = radius of moving circle
				O = offset of "pen point" in moving circle
 
					drawing OUTSIDE of fixed circle
				x = (R+r)*cos(t) - O*cos(((R+r)/r)*t)
				y = (R+r)*sin(t) - O*sin(((R+r)/r)*t)
 
					drawing INSIDE of fixed circle
				x = (R-r)*cos(t) + O*cos(((R-r)/r)*t)
				y = (R-r)*sin(t) - O*sin(((R-r)/r)*t)
			*/
 
			var step:Number = .001 * Math.sqrt(r);
			var t:Number = 360;
 
			_data.lock();
 
			// clear previous
			_data.fillRect(_data.rect, 0x00000000);
 
			while (t > 0) {
				var xp:Number;
				var yp:Number;
 
				if(_outside) {
					xp = (R + r) * Math.cos(t) - O * Math.cos(((R + r) / r) * t);
					yp = (R + r) * Math.sin(t) - O * Math.sin(((R + r) / r) * t);
				} else {
					xp = (R - r) * Math.cos(t) + O * Math.cos(((R - r) / r) * t)
					yp = (R - r) * Math.sin(t) - O * Math.sin(((R - r) / r) * t)
				}
 
				xp += _xoffset;
				yp += _yoffset;
				_data.setPixel32(xp, yp, _color);
 
				t -= step;
			}
 
			_data.unlock();
		}
 
		private function randRange(max:Number, min:Number = 0, decimals:int = 0):Number {
			if (min > max) return NaN;
			var rand:Number = Math.random() * (max-min + Math.pow(10, -decimals)) + min;
			return int(rand * Math.pow(10, decimals)) / Math.pow(10, decimals);
		}
 
		/**
		 * Just some 3d rotation with ease
		 */
		private function frameHandler(event:Event):void {
			var rx:Number = ((stage.mouseX / stage.stageWidth) - .5) * 2;
			var ry:Number = ((stage.mouseY / stage.stageHeight) - .5) * 2;
			var ytr:Number = rx * 30;
			var xtr:Number = ry * 30;
			_spiro.rotationY += (-ytr - _spiro.rotationY) / 10;
			_spiro.rotationX += (xtr - _spiro.rotationX) / 10;
		}
	}
}

3 Comments »

  1. lee says:

    I wish I was as fluent in coding as you to do that so easily.

  2. [...] Spirography. This entry was posted in lifestream. Bookmark the permalink. Comments are closed, but you can [...]

  3. Paul says:

    I think this is one of the coolest things I’ve ever seen on the internet. I’m fascinated with stuff like this, probably partly because I love things like math and fractals and patterns. Great job!

    Now, I’m extremely new to AS3. And I’m trying to make sense of code and syntax, but I’m not real sure about how to do things yet. I don’t know how to take your code above and make it work like your swf file above.

    Is there a complete download file with all the inluded folders? And better yet, is there a tutorial on how to take your coding above and implement it to a final product? Because, at my level, I just cannot figure it out. :/

RSS feed for comments on this post. / TrackBack URI

Leave a Reply

Devon O. Wolfgang

Technical Reviewer of “The Essential Guide to Flash CS4 AIR Development”

Contributing Author of “Flash AS3 for Interactive Agencies”

Senior Software Developer in Dublin, Ireland

Portfolio

Bayer Pixel Bender Filter

Bayer Mosaic Filter in Pixel Bender


Now, seeing as how I’ve been trying to learn some Pixel Bender coding lately[...]

Liquitext

Liquid Text Effect


Haven’t been too active around here lately due to a major on going project at work lately as well as the fact that my wife and I are in the slow process of moving homes. Really, the only free time it seems I have these days is during the lunch hour. So, over the past two days, I came up with this fun little toy during lunches[...]

Google’s Text To Speech Engine in Flash

I’m not sure how I managed to miss this, but I just happened to run across a ‘new’ (well, newish), albeit still unofficial, offering of Google today: text-to-speech. You can see what few details there are on this Techcrunch post. Basically, it just boils down to this though – you send your phrase to be [...]

Quick Sound Effects Generator


Need some beeps, boops, or bops, to go with your UI or games [...]

Quick QuickBox2D Tip II – Collision Detection

Custom collision detection/handling in QuickBox2D [...]

None of This Runs Eternal


No Flash/Actionscript stuff here. Just me rambling about the upcoming Current 93 concert [...]

Playing Around with the New UndoManager

Included in the Flex 4.0 SDK and the, just released, Flash Professional CS5 lies a new hidden little gem of a class: flashx.undo.UndoManager (although the Flex 4.0 SDK’s been out for awhile, I have to admit I didn’t even notice this until I installed Flash CS5 and started poking around the documentation looking for new [...]

Making Waves

In a previous post, “Digging into the Microphone in Flash Player 10.1″, reader David Law asked in the comments how it would be possible to save .wav files to the server. I wasn’t sure right offhand, but thought I’d spend my lunch hour yesterday looking into it. Well, after reading this quick tutorial on Adobe [...]

Some Drawing Fun and a Bad Movie You’ll Never See

Earlier today, Dave Gillem posted a link on Facebook to an incredible Processing based drawing application. Thought I’d have a go at reproducing something similar in Flash. Well, I failed miserably, but the results were still interesting enough to check out. You can play around with it below. Just mouse down to draw in the [...]

Animating Bezier Curves


The other day I got the notion in my head that I wanted to draw some bezier curves in an animated fashion. I’m sure this has been done a million times before, but sometimes reinventing the wheel can be a good learning experience [...]

My God, It’s Full of Stars…


Just a quick little thing I got an idea for (i.e. pretty much ripped off) while browsing Processing examples.

Another Year, Another Look

As a few folks may have noticed, the blog is looking a little bit different as of today [...]

The Webcam Warholizer


Just a little bit of quick Friday fun with webcam and bitmapata…

More With the JiglibFlash Terrain

Playing around a bit more with the animated JiglibFlash terrain idea from my last post, I wanted to see how it would work in conjunction with a webcam. So, needless to say, you’ll need a webcam to check out the results below.

Rockin and Rollin with the JiglibFlash Terrain


Finally got the chance to play around with the new JiglibFlash height map terrain, today, and have to say I am very impressed [...]