require 'opengl' require 'glut' require 'mathn' class MyVector < Array def add(v2) MyVector.new zip(v2).map { |s, o| s+o } end def add!(v2) replace add(v2) end def -@() MyVector.new map { |e| -e } end def ^(v2) MyVector.new [[1, 2], [2, 0], [0, 1]].map { |e1, e2| self[e1]*v2[e2] - self[e2]*v2[e1] } end def *(i) MyVector.new map { |e| e*i } end def normalize! len = Math.sqrt inject(0) { |n, e| e**2 + n } map! { |e| e/len } end def x() self[0] end def y() self[1] end def z() self[2] end def x=(nx) self[0] = nx.to_f end def y=(ny) self[1] = ny.to_f end def z=(nz) self[2] = nz.to_f end end class Viewer attr_reader :pos, :cur, :up # +posini+ array containing the initial position # +world+ is an object whose +render_all+ method will create the world def initialize(world, posini=[0, -2, 0], curini=[0, 1, 0], upini=[0, 0, 1]) @mvunit = 0.1 @rtunit = 5.0 * Math::PI / 360.0 @cosrt = Math.cos(@rtunit) @sinrt = Math.sin(@rtunit) @posini = posini.map { |e| e.to_f } @curini = curini.map { |e| e.to_f } @upini = upini.map { |e| e.to_f } @world = world world.viewer = self if world.respond_to? :viewer= reset end def reset @pos = MyVector.new @posini @cur = MyVector.new @curini @up = MyVector.new @upini end # ag: +/-1 def rotate_z(ag) y = @cur ^ @up @cur = MyVector.new @cur.zip(y).map{ |ex, ey| @cosrt*ex + ag*@sinrt*ey } end def rotate_x(ag) y = @cur ^ @up @up = MyVector.new @up.zip(y).map{ |eu, ey| @cosrt*eu - ag*@sinrt*ey } end def rotate_y(ag) ca = @cosrt sa = ag*@sinrt @cur, @up = @cur.zip(@up).map{ |ec, eu| [ca*ec + sa*eu, -sa*ec + ca*eu] }.transpose @cur = MyVector.new @cur @up = MyVector.new @up end private :rotate_x, :rotate_y, :rotate_z def keyboard(key) case key when ?Q, ?\e: exit 0 when ?j: @pos.add!(-(@cur ^ @up) * @mvunit) when ?l: @pos.add!((@cur ^ @up) * @mvunit) when ?h: @pos.add!(-@up * @mvunit) when ?y: @pos.add!(@up * @mvunit) when ?i: @pos.add!(@cur * @mvunit) when ?k: @pos.add!(-@cur * @mvunit) when ?s: rotate_z -1 when ?f: rotate_z 1 when ?e: rotate_y 1 when ?d: rotate_y -1 when ?t: rotate_x 1 when ?g: rotate_x -1 when ?R: reset else @world.keyboard key if @world.respond_to?(:keyboard) end end def run GLUT.InitDisplayMode(GLUT::DOUBLE | GLUT::RGBA | GLUT::DEPTH) GLUT.Init GLUT.CreateWindow('Vue 3D') GL.ClearColor(0.1, 0, 0.1, 0) GL.FrontFace GL::CW GL.Enable GL::LIGHTING GL.Enable GL::AUTO_NORMAL GL.Enable GL::NORMALIZE GL.Enable GL::DEPTH_TEST GL.Enable GL::LINE_SMOOTH GL.Enable GL::POLYGON_SMOOTH GL.Enable GL::BLEND GL.LightModel GL::LIGHT_MODEL_AMBIENT, [0.2, 0.2, 0.2, 1] GL.LightModel GL::LIGHT_MODEL_LOCAL_VIEWER, [0] GL.Light GL::LIGHT0, GL::AMBIENT, [0.1, 0.1, 0.1, 1] GL.Light GL::LIGHT0, GL::DIFFUSE, [1, 1, 1, 1] GL.Enable GL::LIGHT0 @world.init if @world.respond_to?(:init) GLUT.ReshapeFunc proc { |w, h| GL.Viewport(0, 0, w, h) GL.MatrixMode(GL::PROJECTION) GL.LoadIdentity GLU.Perspective(45.0, w.to_f / h, 0.1, 100.0) GL.MatrixMode(GL::MODELVIEW) } GLUT.DisplayFunc proc { GL.Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT) GL.LoadIdentity GLU.LookAt(*(@pos + (@pos.add @cur) + @up)) @world.render_all GLUT.SwapBuffers } GLUT.KeyboardFunc proc { |key, x, y| keyboard key GLUT.PostRedisplay } GLUT.MainLoop end end class SampleWorld def initialize @light = false end def init GL.Light GL::LIGHT1, GL::AMBIENT, [0, 0, 0, 1] GL.Light GL::LIGHT1, GL::DIFFUSE, [1, 1, 1, 1] # define position here and never after, in order to be relative to the position of the viewer GL.Light GL::LIGHT1, GL::POSITION, [0, 0, 0, 0] GL.Enable GL::LIGHT1 if @light end def keyboard(key) case key when ?L @light = !@light if @light: GL.Enable GL::LIGHT1 else GL.Disable GL::LIGHT1 end end end def render_all GL.Light GL::LIGHT0, GL::POSITION, [2, -5, 20] render_cube 0, 0, 0, 0.4 render_cube 1, 0, 0, 0.99 render_cube 0, 0, 1, 0.1 end def render_cube(x, y, z, w=1) GL.Material GL::FRONT, GL::AMBIENT, [0, 0, 0, 1] GL.Material GL::FRONT, GL::DIFFUSE, [0.1, 0.35, 0.1, 1] GL.Material GL::FRONT, GL::SPECULAR, [0.45, 0.55, 0.45, 1] GL.Material GL::FRONT, GL::SHININESS, 0.25*128.0 GL.PushMatrix GL.Translate x, y, z GLUT.SolidCube w GL.PopMatrix end end if __FILE__ == $0 w = SampleWorld.new v = Viewer.new w v.run end