AS3 Spirograph With Source Code

28 March 2011

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)));
  }
}