JavaFXでドラゴン曲線

パッケージJava製品開発担当の大です。
あけましておめでとうございます。

昨年にひきつづき、今年もブログトップバッターです。昨年のプログラム書初めは、2011という数字にちなんで素数判定でした。今年は辰年ということで、ドラゴン曲線を描いてみます。

プログラム

今回はJavaFXで描いてみました。
JavaFX、昨年末にリリースされたJavaSE 7u2から、ついにOracle JDK同梱になりましたね。
JavaSE 8ではJavaFX 3.0が標準に組み込まれ、Swing/AWTを置き換えていくそうなので、いまから期待しています。

※ 以下のコードは、Windows上のJavaFX 2.0.2で動作確認しています。JavaFX、とくにFXMLまわりの仕様は、まだはっきりしない(ドキュメント化されていない)ところも多く、今後動作が変更されるかもしれませんので、環境やバージョンによって動かない場合はご容赦ください。。。

Main.java:

package tekito;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        Application.launch(Main.class, args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        primaryStage.setTitle("ドラゴン曲線");
        Parent root = FXMLLoader.load(getClass().getResource("Dragon.fxml"));
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Dragon.fxml:

<?xml version="1.0" encoding="UTF-8"?>
<?language javascript?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
<VBox xmlns:fx="http://javafx.com/fxml" style="-fx-padding: 10;-fx-spacing: 10;">
  <children>
    <HBox>
      <children>
        <TextField fx:id="iteration" prefColumnCount="2" text="10" style="-fx-text-alignment: right;" />
        <Label text="次のドラゴン曲線を描画します。枠内でマウスをドラッグしてください。" style="-fx-font-size: 15;" />
        <Button text="すべて消去!" onAction="canvas.children.clear();" />
      </children>
    </HBox>
    <Pane fx:id="canvas" prefWidth="600" prefHeight="600"
          style="-fx-background-color: darkseagreen;-fx-background-radius: 10;" />
    <fx:define>
      <Line fx:id="guide" />
    </fx:define>
    <fx:script source="Turtle.js" />
    <fx:script><![CDATA[
        importPackage(Packages.javafx.scene.shape);

        // ドラゴン曲線描画
        Turtle.prototype.drawDragonCurve = function(len, iter, odd) {
            if (iter < 1) {
                this.forward(len);
                return;
            }
            var angle = odd ? 45 : -45;
            len = Math.sqrt(2) * len / 2;
            this.turnRight(angle);
            this.drawDragonCurve(len, iter - 1, true);
            this.turnLeft(angle * 2);
            this.drawDragonCurve(len, iter - 1, false);
            this.turnRight(angle);
        }

        // マウスボタンが押された
        canvas.onMousePressed = function(event) {
            guide.startX = guide.endX = event.x;
            guide.startY = guide.endY = event.y;
            canvas.children.add(guide);
        }

        // ドラッグ中
        canvas.onMouseDragged = function(event) {
            guide.endX = event.x;
            guide.endY = event.y;
        }

        // マウスボタンが離された
        canvas.onMouseReleased = function(event) {
            canvas.children.remove(guide);
            var xlen = event.x - guide.startX;
            var ylen = event.y - guide.startY;
            var len = Math.sqrt(xlen * xlen + ylen * ylen);
            var rad = Math.atan2(ylen, xlen);
            var turtle = new Turtle(guide.startX, guide.startY, rad);
            turtle.path.clip = new Rectangle(0, 0, canvas.width, canvas.height);
            canvas.children.add(turtle.path);
            turtle.drawDragonCurve(len, iteration.text, true);
        }
    ]]></fx:script>
  </children>
</VBox>

Turtle.js:

importPackage(Packages.javafx.scene.shape);

// タートル
function Turtle(x, y, direction) {
    this.x = x;
    this.y = y;
    this.direction = direction;
    this.isPenDown = true;
    this.path = new Path();
    this.path.elements.add(new MoveTo(x, y));
};

// ペンを下ろす
Turtle.prototype.penDown = function() {
    this.isPenDown = true;

}

// ペンを上げる
Turtle.prototype.penUp = function() {
    this.isPenDown = false;
}

// 右回転
Turtle.prototype.turnRight = function(angle) {
    this.direction += angle * (Math.PI / 180.0);
}

// 左回転
Turtle.prototype.turnLeft = function(angle) {
    this.turnRight(-angle);
}

// 前進
Turtle.prototype.forward = function(len) {
    this.x += Math.cos(this.direction) * len;
    this.y += Math.sin(this.direction) * len;

    if (this.isPenDown) {
        this.path.elements.add(new LineTo(this.x, this.y));
    } else {
        this.path.elements.add(new MoveTo(this.x, this.y));
    }
}

// 後退
Turtle.prototype.back = function(len) {
    this.forward(-len);
}

実行

マウスをドラッグすると、

マウスをドラッグすると。。。

マウスをドラッグすると。。。(クリックで拡大)

ドラッグの開始位置から終了位置まで、ドラゴン曲線を描画します。

ドラゴン曲線を描画します

ドラゴン曲線を描画します(クリックで拡大)

これだけです。

ほんとは、直線から順にヌルヌルと次数を高めていくアニメーションにしようと思ったのですが、JavaFX 2.0ではモーフィングが今のところサポートされていないようなので、あきらめました。

というわけで、本年もHOSをよろしくお願いします。

※ 2012/01/06 コード中で古いAPIを使用していた箇所があったので修正しました。