จากโจทย์ การบ้าน Com sci
เนื่องจากปัญหา ความเร็วของ ruby
ก็เลยต้องเอา rubyinline เข้ามาช่วย
ส่วนการ paint ก็ใช้ ruby-opengl
เริ่มด้วยการสร้าง class จัดการกับการคำนวณก่อน
require 'rubygems' require 'inline' require 'gl' require 'glut' class MandelSet # x,y -> topleft coordinate # x1,y1 -> bottomright coordinate # w -> width (pixel) # h -> height (pixel) def initialize(x,y,x1,y1,w,h) @x = x.to_f @y = y.to_f @rx = (x1 - x).to_f / w @ry = (y - y1).to_f / h @w = w @h = h end ## แปลงค่าพิกัดบน screen ไปเป็น พิกัดที่จะนำไปคำนวณ mandel set def from_screen(x,y) [@x + @rx * x, @y - @ry * y] end def zoom(x,y) [x - 100*@rx, y + 100*@ry, x + 100*@rx, y - 100*@ry, @w, @h] end inline do |builder| builder.c " VALUE mandel(double x, double y) { double x0 = x; double y0 = y; double x2 = x * x; double y2 = y * y; int cnt = 0; int max = 200; while (x2 + y2 < 4 && cnt < max) { y = 2*x*y + y0; x = x2 - y2 + x0; x2 = x*x; y2 = y*y; cnt++; } if (cnt == max || cnt == 0) { return rb_float_new(0.0); } else { return rb_float_new((double)cnt/(double)max); } } " end def to_a ret = [] (0..@w).each do |x| (0..@h).each do |y| c = mandel(*from_screen(x,y)) ret << [x,y,c] end end ret end end
จากนั้นก็ implement ส่วน render
โดยไม่ลืม implement function zoom
เพราะถ้า plot mandelbrot แล้ว zoom ไม่ได้
ก็เท่ากับกินกาแฟพร้อมๆกับบีบจมูก (ไม่ได้กลิ่น)
class MandelPlotter def initialize(x,y,x1,y1,w,h) @w = w @h = h @m = MandelSet.new(x,y,x1,y1,w,h) @result = @m.to_a end def run() STDOUT.sync = TRUE Glut.glutInit Glut.glutInitWindowSize(@w, @h); a = Glut.glutCreateWindow("mandel set") Glut.glutDisplayFunc(disp) Glut.glutReshapeFunc(reshape); Glut.glutMouseFunc(mouseHandle); Glut.glutMainLoop; end def disp() Proc.new { Gl.glClear(Gl::GL_COLOR_BUFFER_BIT) Gl.glBegin(Gl::GL_POINTS) @result.each do |s| c = s[2] Gl.glColor(* chooseColor(c)) Gl.glVertex(s[0],s[1]) end Gl.glEnd Gl.glFlush } end def chooseColor(c) if c > 0.1 && c < 0.3 [c * 1.7 ,0,c * 1.2] elsif c > 0.3 && c < 0.5 [0,c,0] elsif c > 0.5 && c < 0.7 [0,0,c] elsif c > 0.7 && c < 0.9 [0,c*0.3,c*1.1] else [c,c,c] end end def reshape() Proc.new {|w, h| Gl.glViewport(0, 0, w, h) Gl.glMatrixMode(Gl::GL_PROJECTION) Gl.glLoadIdentity Gl.glOrtho(0, w, 0, h, -1, 1) Gl.glScale(1, -1, 1) Gl.glTranslate(0, -h, 0) } end def mouseHandle() Proc.new do |b, state, x, y| if b == Glut::GLUT_LEFT_BUTTON && state == Glut::GLUT_UP nx,ny = @m.from_screen(x,y) ret = @m.zoom(nx,ny) @m = MandelSet.new(*ret) @result = @m.to_a Glut.glutPostRedisplay end end end end
เวลา run ก็
p = MandelPlotter.new(ARGV[0].to_i,ARGV[1].to_i,ARGV[2].to_i,ARGV[3].to_i,400,400) p.run
Note: syntax ที่หลายคนอาจจะไม่เคยเห็น ก็คือ
การ return เป็น array แล้วนำไป pass เป็น parameter
โดยแตก array เป็น argument โดยใช้ operator '*'
Note: ถ้า run แล้วรู้สึกช้า ก็ปรับค่า max จาก 200 ให้เหลือ 30
กระทู้เก่าๆ จะย้ายตามไปในภายหลัง ตอนนี้ปิดการโพสต์กระทู้ไว้ เหลือไว้เฉพาะอ้างอิงเท่านั้น
โอ๊ะ มี inline ด้วย ผมก็เจอปัญหาเดียวกันใน Python มีคนแก้ด้วย Numeric แต่ก็ไม่ได้ดั่งใจ Tkinter ก็ช้ายิ่งกว่า
สงสัย
Proc.newนิดหน่อย ทำไมต้องใช้ครับ เหมือนว่าจะไม่จำเป็น หรือว่าปกติ Ruby แนะนำให้ใช้ซ้อนไว้ในฟังก์ชั่นที่ใช้ Proc.new
เพราะ open-gl มันรับ parameter เป็น Proc object (callback) ครับ
ตรงนี้ต่างกับ python ตรงที่ python
python ทำอย่างนี้ได้เลย
แต่ ruby ทำไม่ได้
จะเห็นว่า มัน evaluate hi ก่อนจะ pass เป็น parameter
Note: สงสัยอยู่เหมือนกันว่า มันอาจจะมีวิธีแปลง method ให้เป็น proc object
แต่ยัง search หาไม่เจอ
อ๋อ ลืมไปนิด ruby จะ return คำสั่งสุดท้ายก่อนจบ method