シーオーリポーツでタートルグラフィックス

製品開発担当の大です。こんにちは。コロナ第6波が猛威をふるっておりますがみなさまお元気でいらっしゃいますでしょうか。

さて今日の話題はタートルグラフィックスです。タートルグラフィックスといえば以前このブログでも書いたことがありましたが、そのときはJavaFXでした(→JavaFXでドラゴン曲線)。今回はシーオーリポーツ for Java Ver.3を使用して書いてみたいと思います。

シーオーリポーツでタートルグラフィックス

タートルグラフィックス

シーオーリポーツはAPIで直線や円などの基本的な図形を描画できるようになっています。そのためタートルグラフィックスを実装するのはそんなに難しくありません。こんな感じです。

class Turtle {
    boolean isPenDown = false;
    double direction = -90; //上
    double x;
    double y;
    CrForm form;
    Pen pen;

    Turtle(CrForm form, Pen pen) {
        // 初期位置は用紙の真ん中にしておく
        this.x = form.getPaperWidth() / 2.0;
        this.y = form.getPaperLength() / 2.0;
        this.form = form;
        this.pen = pen;
    }

    // ペンを上げる
    void penUp() {
        isPenDown = false;
    }

    // ペンを下げる
    void penDown() {
        isPenDown = true;
    }

    // 右回転
    void turnRight(double angle) {
        this.direction += angle;
    }

    // 左回転
    void turnLeft(double angle) {
        turnRight(-angle);
    }

    // 前進
    void forward(double len) {
        var rad = Math.toRadians(direction);
        var newx = Math.cos(rad) * len + x;
        var newy = Math.sin(rad) * len + y;
        if (isPenDown) {
            var line = new CrLine((int) x, (int) y, (int) newx, (int) newy);
            line.setLineColor(pen.getColor());
            line.setLineWidth(pen.getWidth());
            line.setLineStyle(pen.getStyle());
            form.draw(line);
        }
        x = newx;
        y = newy;
    }

    // 後退
    void back(double len) {
        forward(-len);
    }
}
class Pen {
    private Color color;
    private int width;
    private CorLineStyle style;

    Pen(Color color, int width, CorLineStyle style) {
        this.color = color;
        this.width = width;
        this.style = style;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public CorLineStyle getStyle() {
        return style;
    }

    public void setStyle(CorLineStyle style) {
        this.style = style;
    }
}

まずは星形を描画してみます。

public class Main {

    public static void main(String[] args) {
        new Main().createDocument();
    }

    private void createDocument() {
        var job = new CrFileOutJob(CorDocumentType.PDF);
        var draw = new CrDraw();
        try {
            try (var form = CrForm.open(draw, "Empty.cfx")) {
                job.start(draw);
                job.getCrPrinter().setFormSize(form);
                var pen = new Pen(Color.red, 100, CorLineStyle.SOLID);
                var turtle = new Turtle(form, pen);
                drawStar(turtle);
                form.printOut();
                job.end();
            } catch (CrException cex) {
                job.abort();
                throw cex;
            }
        } finally {
            draw.deleteInstance();
        }
    }
    
    private void drawStar(Turtle turtle) {
        turtle.penDown();
        for (int i = 0; i < 5; i++) {
            turtle.forward(12000);
            turtle.turnRight(144);
        }
        turtle.penUp();
    }
}
星形を描画

続いてクロソイド曲線。(呼び出し側はdrawStardrawClothoidになるだけなので省略します)

    private void drawClothoid(Turtle turtle) {
        turtle.penDown();
        IntStream.rangeClosed(-1800, 1800).forEach(angle -> {
            turtle.forward(180);
            turtle.turnRight(angle / 10.0);
        });
        turtle.penUp();
    }
クロソイド曲線

フラクタル図形も問題なしです。

    private void drawTree(Turtle turtle, int depth) {
        if (depth == 0) {
            return;
        }
        turtle.penDown();
        turtle.forward(depth * 200);
        turtle.turnRight(25);
        drawTree(turtle, depth - 1);
        turtle.turnLeft(50);
        drawTree(turtle, depth - 1);
        turtle.turnRight(25);
        turtle.penUp();
        turtle.back(depth * 200);
    }
フラクタル図形(二分木)

カスタム破線パターン

ところで、シーオーリポーツにはあらかじめいくつかの破線パターンが用意されています。それぞれ描画してみます。

    private void drawLineStyles(Turtle turtle, Pen pen) {        
        Stream.of(CorLineStyle.values()).forEach(style -> {
            pen.setStyle(style);
            turtle.penDown();
            turtle.forward(5000);
            turtle.penUp();
            turtle.back(5000);
            turtle.turnLeft(72);
        });
    }
シーオーリポーツのラインスタイル

しかし残念ながら破線の種類をカスタマイズする手段は今のところ用意されていません。「破線の間の空白をもっと広くしたい」などのご要望には応えられていないのです。

そこで、先ほどのタートルグラフィックスを拡張して、カスタム破線パターンを描画できるようにしてみます。破線パターンは、.NET FrameworkのPenクラスのDashPatternAWTのBasicStrokeクラスのdash配列と同様に、線の太さに掛ける乗数の配列で指定することにします。配列の偶数番目は線で描画され、奇数番目は空白になります。

class CustomDashTurtle extends Turtle {
    int index = 0;
    double remain;
    
    CustomDashTurtle(CrForm form, CustomDashPen pen) {
        super(form, pen);
    }

    void forward(double len) {
        if (!isPenDown) {
            super.forward(len);
            index = 0;
            return;
        }
        try {
            double [] dashPattern = ((CustomDashPen)pen).getDashPattern();
            while (true) {
                double l;
                if (remain == 0) {
                    l = dashPattern[index % dashPattern.length] * pen.getWidth();
                } else {
                    l = remain;
                    remain = 0;
                }
                // パターンの偶数番目はペンを下げ、奇数番目は上げる
                if (index % 2 == 0) {
                    penDown();
                } else {
                    penUp();                    
                }
                if (len < l) {
                    super.forward(len);
                    remain = l - len;
                    return;
                } else {
                    super.forward(l);
                    len -= l;
                    index++;
                }
            }
        } finally {
            // ペンの状態を戻しておく
            penDown();
        }
    }
}
class CustomDashPen extends Pen {
    private double [] dashPattern;
    
    CustomDashPen(Color color, int width, double [] dashPattern) {
        super(color, width, CorLineStyle.SOLID);
        this.dashPattern = dashPattern;
    }
    
    public double [] getDashPattern() {
        return dashPattern;
    }
}

それでは先ほどと同じ星形を描画してみます。drawStarメソッドは変更ありません。破線パターンは空白部分を広めにしてみました。

var pen = new CustomDashPen(Color.red, 200, new double [] {2.0, 5.0});
var turtle = new CustomDashTurtle(form, pen);
drawStar(turtle);
星形(カスタム破線)

クロソイド曲線。破線パターンは空白部分の広い点線です。

var pen = new CustomDashPen(Color.blue, 50, new double [] {0.1, 3.0});
クロソイド曲線(カスタム破線)

二分木。破線パターンは一点鎖線です(これはシーオーリポーツで用意されている一点鎖線と同じパターンです)

var pen = new CustomDashPen(new Color(0x22, 0x8b, 0x22), 100, new double [] {2.0, 2.0, 0.1, 2.0});
二分木(カスタム破線)

というわけでカスタム破線パターンにも対応できましたね!

こちらからダウンロードできる体験版で上記のプログラムを動作させることができます。ぜひ試してみてください。