らんだむな記憶

blogというものを体験してみようか!的なー

ということで、Bézier曲線で囲まれた領域の面積とか(2)

ということで、Bézier曲線で囲まれた領域の面積とか - らんだむな記憶で面積計算のための式を導いた。が検証はしていなかった。
まぁ、計算とか苦手なんで間違っている可能性は大なのだが!

すでに準備はできている。rubyとBézierと(2) - らんだむな記憶で十分にBézier曲線で円を近似できることを感覚的に見たので、半径1の円を近似して、その面積を求めると、円周率に十分近い値が求まるはずだ。真円よりはちょっと膨らむということなのだから、円周率に近いけど、ちょっと大きい値、になるはずだ。
さて、rubyで書こう。お料理番組ではそう言った次の瞬間にカメラが横に移動して完成したボールが出てくる。勿論、ここでも同様に書いた後のコードを貼り付ける。しかも、ちょっと富豪的プログラミング(念のため引用:富豪的プログラミング)してみたw 今回はUIとか全然関係ないけどね!インスタンスなんかどんどん生成すればいいんだよ!GCが回収するから!(とかすると結構コストが嵩んで遅くなるんだけどね。破壊的メソッドを使ってインスタンスを使いまわしたほうが速いと思う。)

#! /usr/bin/ruby -Ku

K = 4.0 * (Math.sqrt(2) - 1) / 3

class Point
    attr_reader :x, :y

    def initialize(x, y)
        @x, @y = x, y
    end

    def coord
        return @x, @y
    end

    def +(rhs)
        return Point.new(@x + rhs.x, @y + rhs.y)
    end

    def *(scalar)
        return Point.new(@x * scalar, @y * scalar)
    end

    def cross_product(rhs)
        @x * rhs.y - rhs.x * @y
    end
end

class Bezier
    def initialize(p0, p1, p2, p3)
        @p0, @p1, @p2, @p3 = p0, p1, p2, p3
    end

    def pos(t)
        return (@p0 * (1-t)**3 + @p1 * 3 * (1-t)**2 * t + @p2 * 3 * (1-t) * t**2 + @p3 * t**3).coord
    end

    # 面積計算のためのある量の20倍の値を返す。直接的に面積を与えるものではない。
    def twenty_times_area_value_of_segment
        6 * @p0.cross_product(@p1) + 3 * @p0.cross_product(@p2) + @p0.cross_product(@p3) + 3 * @p1.cross_product(@p2) +  3 * @p1.cross_product(@p3) + 6 * @p2.cross_product(@p3)
    end
end

class BezPath
    def initialize
        @bez_segments = []
    end

    def add(bez)
        @bez_segments << bez

        self
    end

    def area
        twenty_times_area = 0
        @bez_segments.each do |seg|
            twenty_times_area += seg.twenty_times_area_value_of_segment
        end

        Float(twenty_times_area) / 20
    end
end


################################################################################

b1 = Bezier.new(Point.new(1,  0), Point.new(1,   K), Point.new(K,   1), Point.new(0,  1))
b2 = Bezier.new(Point.new(0,  1), Point.new(-K,  1), Point.new(-1,  K), Point.new(-1, 0))
b3 = Bezier.new(Point.new(-1, 0), Point.new(-1, -K), Point.new(-K, -1), Point.new(0, -1))
b4 = Bezier.new(Point.new(0, -1), Point.new(K,  -1), Point.new(1,  -K), Point.new(1,  0))

print "#{BezPath.new.add(b1).add(b2).add(b3).add(b4).area}\n"

Bézierの座標計算で Point×数値の順にしないとならんのは残念。数値×Pointも実現するには、数値型のクラスに一時的にPoint型変数をも引数として受け取る形での四則演算子オーバーロードをかまさんとならないが、それをするには特異メソッドや特異クラスのような "黒魔術" すれすれな感じになって嫌だからやらない。やりすぎるとドラッグと同じで廃人になりそうだw

Rubyの黒魔術でも読んで

"ムスカ".balse!

したらいいと思うw

なお、面積計算の結果は、

$ ruby bezier_test.rb
3.1424723326565074

だった。円周率は3もとい、3.1415以下略であることを考えると、一応期待する結果かなということで、たぶん元の面積計算式も恐らくあっていたのだろう。
なお、$\pi$の連分数第一近似は$\frac{22}{7} = 3.142857...$なのだが、案外こっちに近い値が出たなぁ、とかかんとか。いや、ただの偶然だけど。