OpenGL で正多面体を回転させてみる(Ruby)
Ruby 用に OpenGL を手軽に使うライブラリ「miniopengl.rb」を書いたので、その例として 5つの正多面体すべてを回転させてみました。
正多面体のデータはここより頂戴しました。ありがとうございます。polyhedrons_obj.zip をダウンロードして解凍し、フォルダを下のコードと同じディレクトリに置いて下さい。
コードは以下です。miniopengl.rb はこのコードと同じディレクトリに配置して下さい。当然ながら OpenGL が使えるようにしなければなりません。必要な準備はここを参照して下さい。また、並列処理を行う Gem 'parallel' を使っています。これはインストール($ gem install parallel)しておいて下さい。
動作は Linux Mint 18.2, Ruby 2.3.3 で確認しました。Gem 'parallel' でおそらく Kernel.#fork を使っているので、Windows では動かないと思われます。
require_relative 'miniopengl' require 'parallel' filenames = [] 1.upto(5) {|i| filenames << "./polyhedrons_obj/r" + ("%02d" % i) + ".obj"} data = [] filenames.each do |fn| open(fn, "r") do |io| ar = [[], []] io.each_line do |l| l.chomp! a = l[2..-1].split(" ") (l[0] == "v") ? ar[0] << a.map(&:to_f) : ar[1] << a.map {|i| i.to_i - 1} end data << ar end end Parallel.each(data, in_processes: 5) do |vertex, apexes| MiniOpenGL.app width: 400, height: 400 do clear_color(0, 0, 0.3) draw do clear color(0, 1, 0) glEnableClientState(GL_VERTEX_ARRAY) glVertexPointer(3, GL_DOUBLE, 0, vertex.flatten) apexes.size.times {|i| glDrawElements(GL_LINE_LOOP, apexes[i].size, GL_UNSIGNED_BYTE, apexes[i])} display end reshape do |w, h| viewport(0, 0, w, h) init_projection perspective(30, w / h.to_f, 3, 10) init_modelview look_at(3, 4, 5, 0, 0, 0, 0, 1, 0) end repeat(30) do modelview rotate(5, 0, 1, 0) redisplay end end end
Gem 'parallel' によって 5つのウィンドウが同時に立ち上がります。
※Gem 'parallel' に関しては下を参照。
rubyで簡単並列処理(Parallel)
マルチスレッド/プロセスまとめ(Ruby編)
Ruby のサンプルコードについて
結城先生が仰っていることは大事なことだと思います。いちばん大変なのは初心者のときです。とにかく簡単なコードを動かす(つまりは 'Hello World!')のがわからない。僕はプログラミングに関してまわりに誰も聞く人がいなかったし今もいないので、(いまでもまだ)いつも簡単なことがわからない。そういうことが、初心者を卒業してしまった人にはなかなかわかりにくいのですね。
でもこれは自戒なのである。自分も説明するのが結構めんどうでサボったりするのだよなあ…。結城先生の本はいつも親切である。

- 作者: 結城浩
- 出版社/メーカー: 筑摩書房
- 発売日: 2013/04/11
- メディア: 文庫
- この商品を含むブログ (30件) を見る
Ruby は「A Programmer's Best Friend」ってことなのだが、言語自体は初心者にも friendly なのに、それを動かすことに関しては初心者にあまり親切でないと思う。特に Windows で使うのが大変。僕が初めて Ruby を使ったのは Windows でだったのだが、とりあえず使うエディタがなくて困った記憶がある。ぐぐって何とか「サクラエディタ」をインストールして使ったのだった。このあたり、絶対つまらぬところで損をしていると思う。ここは Python を見習った方がいいのではないか。
RubyGem 'Oekaki' ver.0.1.0 Release
RubyGem 'oekaki'のヴァージョン 0.1.0 をリリースしました。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
新しいメソッド Tool#clear, Tool#get_window_size, Event#window_changed を追加しました。
- Tool#clear(color = nil) は画面のクリアです。color はクリアする色です。
- Tool#get_window_size は現在の画面の大きさを配列 [width, height] に入れて返します。
- Event#window_changed {...} はウィンドウの状態が替わったとき(最大化など)、ブロック内が呼ばれます。
- デフォルトでウィンドウのリサイズができないようにしました。リサイズしたい場合は、
Oekaki.app(resizable: true) {..}のように指定して下さい。
また、ヴァージョン 0.0.11 ではメソッド Tool#circle が追加されています。
- Tool#circle(fill, x, y, r, color = nil) は中心 (x, y)、半径 r の円を書きます。fill が true ならば塗りつぶし、false ならば円周のみ描画されます。
これらに対応した
GTK+ で落書き 11(Ruby) - Camera Obscura
を書き直してみました。ウィンドウの最大化に対応しています。コードは下です。
円が降ってくる(スクリーンセーバーもどき) · GitHub
GTK+ で落書き 11(Ruby)
スクリーンセーバーみたいなものを Ruby で描いてみました。円が大きくなったり小さくなったりしながら落下していきます。
自画自賛ですけれど、これ結構好きです。
描画には自作の Gem 'oekaki' を使っています。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
require 'oekaki' Width, Height = if ARGV.size == 2 ARGV.map(&:to_i) else [1000, 700] end Max_r, Min_r = 40, 10 ColorMax = 65535 MaxNum = 60 class Circle def initialize(ob) @slot = ob renewal @y = rand(Height) end def renewal @max_r = rand(Max_r - Min_r) + Min_r @x = rand(Width) @y = -rand(@max_r) @color = [rand(ColorMax), rand(ColorMax), rand(ColorMax)] @fall_step = 1 + rand * 3 @r = 1 @r_step = rand * 0.2 + 0.8 end def paint @slot.color(@color[0], @color[1], @color[2]) @slot.circle(true, @x, @y, @r) @y += @fall_step @r += @r_step @r_step *= -1 if @r > @max_r or @r < 1 renewal if @y > Height + Max_r true end end Oekaki.app width: Width, height: Height do circles = [] MaxNum.times {circles << Circle.new(self)} draw do color(0, 0, 0) rectangle(true, 0, 0, Width, Height) end timer(80) do color(0, 0, 0) rectangle(true, 0, 0, Width, Height) circles.each(&:paint) end end
円によって落下速度や点滅測度が微妙に変えてあります。円の総数はいつも同じで、定数 MaxNum に入っています。
Gem 'oekaki' のバージョンは最新の 0.0.11 が必要です。古いバージョンならば、上の @slot.circle(true, @x, @y, @r) を @slot.arc(true, @x - @r, @y - @r, 2 * @r, 2 * @r, 0, 64 * 360) に代えて下さい。それで動きます。
※追記
以上は Linux Mint 18.2, Ruby 2.3.3 で確認しましたが、Windows 8.1, Ruby 2.2.2 [i386-mingw32] でもそのまま動くことを確認しました。ただ、Windows 版ではなぜか画面がちらつきますし、Linux Mint で実行したよりも動きがなめらかでないです。
GTK+ でスクロールするテキスト・ウィンドウを作る(Ruby)
Ruby で GTK+ を使ってテキストを表示させるウィンドウを作ってみました。Gem 'gtk2' を使っています。
window = ScrolledTextWindow.open 5.times do |i| window.write "send #{i}" window.write " ok!\n" sleep(rand(2) + 1) end window.close
これでこんな風に表示されます。

write で改行のたびにリアルタイムで書き込まれます。スクロールもします。ただし、残念ながら close してもウィンドウは消えません。ウィンドウのボタンで消去して下さい。
ウィンドウの縦横幅、タイトルを指定することもできます。ScrolledTextWindow.open(width: 300, height: 200, title: "Window") みたいな感じ。
scrolled_text_window.rb
require 'gtk2' class ScrolledTextWindow def self.open(width: 500, height: 200, title: "Text Window") #IO{read, write}, Gtk::Window{w}, Gtk::ScrolledWindow{sw}, Gtk::TextView{t} #Gtk::TextBuffer{b}, control read, write = IO.pipe control = Object.new control.define_singleton_method(:write) do |text| write.print text end control.define_singleton_method(:close) do write.close end fork do write.close w = Gtk::Window.new w.signal_connect("destroy") {Gtk.main_quit} w.set_size_request(width, height) w.title = title sw = Gtk::ScrolledWindow.new sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) t = Gtk::TextView.new t.modify_font(Pango::FontDescription.new("12")) sw.add(t) w.add(sw) b = t.buffer Thread.new(t, b) do |t, b| loop do b.insert_at_cursor(read.gets) t.scroll_mark_onscreen(b.create_mark(nil, b.end_iter, true)) end end Gtk.quit_add(0) {read.close} w.show_all Gtk.main end read.close control end end
※参考
GTK+ で簡単なテキスト入力(Ruby) - Marginalia
noanoa 日々の日記 : Ruby/GTK2,GTK3 プログラミング Tips(3)- ボタン
フロイド−ワーシャル・アルゴリズム(Ruby)

フロイド−ワーシャル・アルゴリズムは、グラフの全点から全点へのすべての最短経路を求めます。重みは負値を許しますが、負閉路は許しません。
上のようなグラフは次のようなハッシュで表現されます。頂点を表わすオブジェクトは何でもかまいません。
g = {a: {b: 3, c: 8}, b: {d: 1}, c: {b: 4}, d: {a: 2, c: -5}}
これは Hash#to_graph によって次のような配列に変換されます。
[[0, 3, 8, Infinity], [Infinity, 0, Infinity, 1], [Infinity, 4, 0, Infinity], [2, Infinity, -5, 0]]
メソッドの内部では @tr = {0=>:a, 1=>:b, 2=>:c, 3=>:d} というハッシュによって、頂点と頂点をあらわす番号(頂点番号)との間が関係づけられています。配列[from][to] は、from, to の頂点番号により、経路の重みを表現しています。Infinity は経路が存在しません。
メソッド Array#floyd_warshall は、その配列から全点最短経路を求めます。返り値は [shortest, pred] で、shortest も pred も(頂点数を n とすると) (n + 1) × n × n の大きさの配列です。具体的には
shortest = [[[0, 3, 8, Infinity], [Infinity, 0, Infinity, 1], [Infinity, 4, 0, Infinity], [2, Infinity, -5, 0]], [[0, 3, 8, Infinity], [Infinity, 0, Infinity, 1], [Infinity, 4, 0, Infinity], [2, 5, -5, 0]], [[0, 3, 8, 4], [Infinity, 0, Infinity, 1], [Infinity, 4, 0, 5], [2, 5, -5, 0]], [[0, 3, 8, 4], [Infinity, 0, Infinity, 1], [Infinity, 4, 0, 5], [2, -1, -5, 0]], [[0, 3, -1, 4], [3, 0, -4, 1], [7, 4, 0, 5], [2, -1, -5, 0]]] pred = [[[nil, 0, 0, nil], [nil, nil, nil, 1], [nil, 2, nil, nil], [3, nil, 3, nil]], [[nil, 0, 0, nil], [nil, nil, nil, 1], [nil, 2, nil, nil], [3, 0, 3, nil]], [[nil, 0, 0, 1], [nil, nil, nil, 1], [nil, 2, nil, 1], [3, 0, 3, nil]], [[nil, 0, 0, 1], [nil, nil, nil, 1], [nil, 2, nil, 1], [3, 2, 3, nil]], [[nil, 0, 3, 1], [3, nil, 3, 1], [3, 2, nil, 1], [3, 2, 3, nil]]]
となります。いずれも最後の 2次元行列が求めるべき回答です。shortest の最後は重みの最小値を、pred の最後はたどるべき経路を表します。例えば頂点 0 から頂点 2 の重みの最小値は shortest[4][0][2] = -1 となり、最短経路は 0→1→3→2 となります。最短経路は後ろから pred[4][0][2] = 3 で頂点 3、pred[4][0][3] = 2 で頂点 2、pred[4][0][2] = 0 で頂点 0 という具合です。
この結果、この場合での最短経路中の最大値は shortest[4][2][0] = 7 であり、つまりは頂点 2 から 0 の経路ということがわかります。
メソッド Hash#order(from, to) は Array#floyd_warshall を呼び出したあと、頂点 from から to までの最短経路(経路番号ではありません)を順に配列に入れて返します。
floyd_warshall.rb
class Array def floyd_warshall #Float or Integer or Rational{ar[][], shortest[][][], a}, Integer{pred[][][]}, size ar = self size = ar.size class << ar def include?(u, v) self[u][v] != Float::INFINITY and !self[u][v].zero? end end shortest = Array.new(size + 1) {Array.new(size) {Array.new(size)}} pred = Array.new(size + 1) {Array.new(size) {Array.new(size)}} size.times do |u| size.times do |v| shortest[0][u][v] = ar[u][v] pred[0][u][v] = ar.include?(u, v) ? u : nil end end size.times do |x| size.times do |u| size.times do |v| if shortest[x][u][v] > (a = shortest[x][u][x] + shortest[x][x][v]) shortest[x + 1][u][v] = a pred[x + 1][u][v] = pred[x][x][v] else shortest[x + 1][u][v] = shortest[x][u][v] pred[x + 1][u][v] = pred[x][u][v] end end end end [shortest, pred] end end class Hash def to_graph #Object{node[]}, Float or Integer or Rational{ar[][]} #Hash{@tr[Integer]: Object} node = keys @tr = node.map.with_index {|key, i| [i, key]}.to_h ar = Array.new(size) {Array.new(size, Float::INFINITY)} size.times do |x| self[@tr[x]].each do |g| ar[x][node.index(g[0])] = g[1] end ar[x][x] = 0 end ar end def order(from, to) #Object{from, to} -> #Object{ans[]}, Hash{tr[Object]: Integer} #Integer{pred[][][], ar[][], from1, nxt}, shortest shortest, pred = to_graph.floyd_warshall ar = pred[-1] tr = @tr.invert from1 = tr[from] ans = [to] nxt = ar[from1][tr[to]] while nxt != from1 ans.unshift(@tr[nxt]) nxt = ar[from1][nxt] end [@tr[nxt]] + ans end end if __FILE__ == $0 g = {a: {b: 3, c: 8}, b: {d: 1}, c: {b: 4}, d: {a: 2, c: -5}} p g.order(:a, :c) #=>[:a, :b, :d, :c] end

- 作者: トーマス・H・コルメン,長尾高弘
- 出版社/メーカー: 日経BP社
- 発売日: 2016/03/11
- メディア: 単行本
- この商品を含むブログ (5件) を見る
melborne さんの Object#sequence メソッド(Ruby)
RubyにおけるシーケンスはObject#repeatに任せなさい!
いまはもう更新されていませんが、melborne さんの「hp12c」というブログがあります。この方はおもしろい Rubyist で、僕はときどきヒマに任せてこれを読むのが好きなのですね。文章がうまいし、特殊な才能があるように思われます。
で、この方の考えられた、Ruby の Object#sequence メソッドというのがあるのですよね(参照)。コードはこんなのです。
sequence.rb
class Object def sequence(init = true) x = self Enumerator.new do |y| y << x if init loop {y << (x = yield x)} end end end
わかりますかね。外部イテレータを使った、一種の反復「無限リスト」ジェネレータともいえるでしょうか。もちろんこれは「リスト」(Ruby の配列)だけに留まるものではありません。とにかく「無限リスト」の Ruby での実装として、すばらしいのではないかと思ったのです。実際に Matz もツイッターで反応したくらいですからね。Feature リクエストは結局採用されなかったようですが、それでもおもしろいと思います。
で、どんな風に使うかなのですが、実際に作者がブログ記事でたくさんの例を挙げておられるので、それを御覧下さい。ってそれだけではそっけなさすぎるので、例えば(初項 3、項差 5の)等差数列は
p 3.sequence {|x| x + 5}.take(10) #=>[3, 8, 13, 18, 23, 28, 33, 38, 43, 48]
という感じです。超簡潔ですね。
p [1, 1].sequence {|a, b| [b, a + b]}.take(15).map(&:first) #=>[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
すばらしい。
単なる「無限リスト」もこんな感じ。
p 7.sequence(&:succ).take(20) #=>[7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
7 から始まる「無限リスト」ですね。
このメソッドはすごく読みやすくて「きれいな」印象なのですよね。Ruby らしい感じです。
なお、もともとのメソッド名は repeat ですが、作者が Feature リクエストに採用した sequence で統一しました。
何で採用されなかったのですかね。あんまり使われなさそうだからかなあ。
よく考えてみると、Object クラスに入れるのは大袈裟かも知れませんね。Numeric, Array, String くらいで充分な気もする(あ、それだと自作のクラスに使えなくなっちゃうな)。それかせめて Kernel か。採用されなかったのはそのあたりもあるかな。