package { import flash.display.Sprite; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFieldAutoSize; [SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#FFFFFF")] /** * graphics.drawGraphicsData のための回転対称による美的表現 * 解説ページ:http://aquioux.blog48.fc2.com/blog-entry-597.html * @author YOSHIDA, Akio */ public class Main extends Sprite { private var numOfAxis:uint; // 対称軸数(この値によって360度を分割し、単位角度を求める) private var numOfNode:uint; // 単位角度内に置かれる Node オブジェクトの数 private var time:Number = 1.75; // RotationalSymmetry クラスの Tweener の所要時間 private var symmetry:RotationalSymmetry; private var textField:TextField; public function Main() { textField = new TextField(); textField.autoSize = TextFieldAutoSize.LEFT; textField.selectable = false; addChild(textField); stage.addEventListener(MouseEvent.CLICK, clickHandler); clickHandler(null); } private function clickHandler(event:MouseEvent):void { if (symmetry) { symmetry.kill(); removeChild(symmetry); symmetry = null; } numOfAxis = Math.random() * 12 + 2; numOfNode = Math.random() * 5 + 2; symmetry = new RotationalSymmetry(numOfAxis, numOfNode , time); addChild(symmetry); symmetry.build(); symmetry.run(); textField.text = "回転対称軸数:" + symmetry.numOfAxis + "\n単位角度内ノード数:" + symmetry.numOfNode; } } } import caurina.transitions.AuxFunctions; import caurina.transitions.Tweener; import flash.display.GraphicsEndFill; import flash.display.GraphicsPath; import flash.display.GraphicsPathCommand; import flash.display.GraphicsSolidFill; import flash.display.GraphicsStroke; import flash.display.IGraphicsData; import flash.display.Sprite; import flash.events.TimerEvent; import flash.geom.Matrix; import flash.geom.Point; import flash.utils.Timer; /** * graphics.drawGraphicsData を使った回転対称 * @author Yoshida, Akio */ class RotationalSymmetry extends Sprite { private var _numOfAxis:uint; // 対称軸数(この値によって360度を分割し、単位角度を求める) private var _numOfNode:uint; // 単位角度内に置かれる Node オブジェクトの数 public function get numOfAxis():uint { return _numOfAxis; } public function get numOfNode():uint { return _numOfNode; } // Node オブジェクトを格納する Vector private var nodeVector:Vector.<Node>; // Matrix.translate で使用 private var offsetX:Number; private var offsetY:Number; // Node 座標を変換するための Matrix private var convMatrix:Matrix; // graphicsData 用 private var graphicsData:Vector.<IGraphicsData>; private var fill:GraphicsSolidFill; // 塗り private var path:GraphicsPath; // パス private var time:Number; // Tweener の Tween 所要時間 private var timer:Timer; public function RotationalSymmetry(numOfAxis:uint, numOfNode:uint, time:Number) { _numOfAxis = Math.max(2, numOfAxis); _numOfNode = Math.max(2, numOfNode); this.time = Math.max(0.5, time); if (_numOfAxis == 2) { _numOfNode *= 2; } TweenerShortcuts.init(); // Tweener 用のカスタムショートカット } // 要素の構築 public function build():void { if (stage) { buildNode(); buildOffset(); buildMatrix(); buildGraphicsData(); } else { throw new Error("いずれかの DisplayObjectContainer に addChild してください"); } } // 実行 public function run():void { timer = new Timer(time * 1.25 * 1000); timer.addEventListener(TimerEvent.TIMER, timerHandler); timer.start(); timerHandler(null); } // 削除用 public function kill():void { timer.stop(); timer.removeEventListener(TimerEvent.TIMER, timerHandler); } // Node オブジェクト生成 private function buildNode():void { var max:Number = stage.stageWidth / 2; var min:Number = 10; var n:uint = _numOfNode; nodeVector = new Vector.<Node>(); for (var i:uint = 0; i < n; i++) { nodeVector.push(new Node(max, min)); } } // オフセット計算 private function buildOffset():void { offsetX = stage.stageWidth / 2; offsetY = stage.stageHeight / 2; } // 変換マトリクス生成 private function buildMatrix():void{ convMatrix = new Matrix(); convMatrix.translate( -offsetX, -offsetY); convMatrix.rotate(Math.PI * 2 / _numOfAxis); convMatrix.translate(offsetX, offsetY); } // graphics.drawGraphicsData の引数生成 private function buildGraphicsData():void { // 塗りの定義 fill = new GraphicsSolidFill(Math.random() * 0xFFFFFF); // commands の type を決定 var commandType:int = GraphicsPathCommand.LINE_TO; var divider:uint = 1; if ((_numOfAxis * _numOfNode) % 2 == 0) { // 対称軸数とノード数の積が偶数(正偶数角形)のとき if (Math.random() < 0.75) { // 75% の確立で CURVE_TO commandType = GraphicsPathCommand.CURVE_TO; divider = 2; } } // パスの定義 // コマンドの定義 var commands:Vector.<int> = new Vector.<int>(); commands.push(GraphicsPathCommand.MOVE_TO); var n:uint = _numOfAxis * _numOfNode / divider; for (var i:uint = 0; i < n; i++) { commands.push(commandType); } // 座標データの定義 var data:Vector.<Number> = new Vector.<Number>(); // コマンド+座標データをパッケージング path = new GraphicsPath(commands, data); // 描画データをパッケージング graphicsData = new Vector.<IGraphicsData>(); graphicsData.push(fill); graphicsData.push(path); graphicsData.push(new GraphicsEndFill()); } // タイマーハンドラ(Tweener 定義) private function timerHandler(event:TimerEvent):void { // 色 Tweener.addTween(fill, { color:0xFFFFFF * Math.random(), time:time, transition:"easeInCubic" }); // 座標 var node:Node = nodeVector[0]; var next:Point = node.next; Tweener.addTween(node, { onUpdate:update, x:next.x, y:next.y, time:time, transition:"easeOutCubic" }); var n:uint = nodeVector.length; for (var i:uint = 1; i < n; i++) { node = nodeVector[i]; next = node.next; var transition:String = (i % 2) ? "easeInCubic" : "easeOutCubic"; Tweener.addTween(node, { x:next.x, y:next.y, time:time, transition:transition }); } } // Tweener が onUpdate のタイミングで呼び出す private function update():void { render(createPathData()); } // path の data を計算 private function createPathData():Vector.<Number> { var localConvMatrix:Matrix = convMatrix.clone(); var resultPoint:Point; var data:Vector.<Number> = new Vector.<Number>(); var prevXVector:Vector.<Number> = new Vector.<Number>(); var prevYVector:Vector.<Number> = new Vector.<Number>(); // 第1単位角度の座標 var n:uint = nodeVector.length; for (var i:uint = 0; i < n; i++) { prevXVector[i] = nodeVector[i].x + offsetX; prevYVector[i] = nodeVector[i].y + offsetY; data.push(prevXVector[i], prevYVector[i]); } // 第2単位角度以降の座標 var m:uint = n; n = _numOfAxis; for (i = 1; i < n; i++) { for (var j:uint = 0; j < m; j++) { resultPoint = localConvMatrix.transformPoint(new Point(prevXVector[j], prevYVector[j])); prevXVector[j] = resultPoint.x; prevYVector[j] = resultPoint.y; data.push(prevXVector[j], prevYVector[j]); } } data.push(data[0], data[1]); return data; } private function render(data:Vector.<Number>):void { path.data = data; graphics.clear(); graphics.drawGraphicsData(graphicsData); } } import flash.geom.Point; class Node { public var x:Number = 0.0; public var y:Number = 0.0; private var max:int; private var min:int; public function Node(max:int, min:int) { this.max = max; this.min = min; } public function get next():Point { var radius:uint = randomInt(max, min); var degree:uint = randomInt(360); return Point.polar(radius, degree * Math.PI / 180); } private function randomInt(max:int, min:int = 0):int { return Math.random() * (max - min) + min; } } import caurina.transitions.Tweener; import caurina.transitions.AuxFunctions; import flash.display.GraphicsSolidFill; class TweenerShortcuts { public function TweenerShortcuts () {} public static function init(): void { // Tweener にカスタムメソッドを追加(GraphicsSolidFill 用) // caurina.transitions.properties.TextShortcut.as を参考にした Tweener.registerSpecialPropertySplitter("color", _generic_splitter, ["r", "g", "b"]); Tweener.registerSpecialProperty("r", _property_get, _property_set, ["r"]); Tweener.registerSpecialProperty("g", _property_get, _property_set, ["g"]); Tweener.registerSpecialProperty("b", _property_get, _property_set, ["b"]); } // Tweener カスタムメソッド(GraphicsSolidFill 用) // caurina.transitions.properties.TextShortcut.as を参考にした public static function _generic_splitter(p_value:Number, p_parameters:Array):Array { var nArray:Array = new Array(); nArray.push({name:p_parameters[0], value:AuxFunctions.numberToR(p_value)}); nArray.push({name:p_parameters[1], value:AuxFunctions.numberToG(p_value)}); nArray.push({name:p_parameters[2], value:AuxFunctions.numberToB(p_value)}); return nArray; } public static function _property_get (p_obj:Object, p_parameters:Array, p_extra:Object = null):Number { var fill:GraphicsSolidFill = GraphicsSolidFill(p_obj); var colorComponent:String = p_parameters[0]; if (colorComponent == "r") return AuxFunctions.numberToR(fill.color); if (colorComponent == "g") return AuxFunctions.numberToG(fill.color); if (colorComponent == "b") return AuxFunctions.numberToB(fill.color); return NaN; } public static function _property_set(p_obj:Object, p_value:Number, p_parameters:Array, p_extra:Object = null):void { var fill:GraphicsSolidFill = GraphicsSolidFill(p_obj); var colorComponent:String = p_parameters[0]; if (colorComponent == "r") fill.color = (fill.color & 0x00ffff) | (p_value << 16); if (colorComponent == "g") fill.color = (fill.color & 0xff00ff) | (p_value << 8); if (colorComponent == "b") fill.color = (fill.color & 0xffff00) | p_value; } } graphics.drawGraphicsData のための回転対称による美的表現