Skip to content

Instantly share code, notes, and snippets.

@tociyuki tociyuki/cubpview.rb
Last active Jun 23, 2017

Embed
What would you like to do?
Cubic Panorama Viewer from 6 JPEG faces by Ruby and OpenGL GLUT
#!/usr/bin/env ruby
# cubpview.rb -- Cubic Panorama Viewer
#
# usage: ruby cubpview.rb prefix
#
# depends:
#
# gem install opengl
# gem install glu
# gem install glut
# gem install rmagick
#
# show a cubic panorama from the set of six image/jpeg files:
#
# ${prefix}0.jpg front
# ${prefix}1.jpg right
# ${prefix}2.jpg back
# ${prefix}3.jpg left
# ${prefix}4.jpg top
# ${prefix}5.jpg bottom
#
# move camera direction by mouse dragging or the key press:
#
# 'h': rotate left
# 'j': rotate down
# 'k': rotate up
# 'l': rotate right
# 'u': reset camera direction
# 'i': zoom in
# 'o': zoom out
# 'q': quit
#
# Copyright 2017 by MIZUTANI Tociyuki
# License: Same as ruby itself
require 'opengl'
require 'glu'
require 'glut'
require 'rmagick'
module Panorama
class Cubic
include Gl, Glu, Glut
WINDOW_WIDTH = 480
WINDOW_HEIGHT = 360
Point = Struct.new(:x, :y)
def initialize(prefix)
@prefix = prefix || ''
@angle_step = 5 # degree
@xztheta = 0 # degree
@yzphi = 0 # degree
@fov_step = 10
@fov = 80
@texture = nil
@point_start = Point.new(0, 0)
@angle_start = Point.new(@xztheta, @yzphi)
end
def run()
init_window("Cubic Panorama - #{@prefix}", WINDOW_WIDTH, WINDOW_HEIGHT)
init_cubic_panorama(@prefix)
glutMainLoop()
end
private
def init_window(title, width, height)
glutInit()
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
glutInitWindowSize(width, height)
glutCreateWindow(title)
glutDisplayFunc(self.method(:handle_display).to_proc)
glutMouseFunc(self.method(:handle_mouse).to_proc)
glutMotionFunc(self.method(:handle_motion).to_proc)
glutKeyboardFunc(self.method(:handle_keypress).to_proc)
end
def init_cubic_panorama(prefix)
glEnable(GL_TEXTURE_2D)
@texture = glGenTextures(6)
load_surface("#{prefix}0.jpg", @texture[0]) # front
load_surface("#{prefix}1.jpg", @texture[1]) # right
load_surface("#{prefix}2.jpg", @texture[2]) # back
load_surface("#{prefix}3.jpg", @texture[3]) # left
load_surface("#{prefix}4.jpg", @texture[4]) # top
load_surface("#{prefix}5.jpg", @texture[5]) # bottom
end
def load_surface(src, texture)
img = Magick::Image.read(src).first
check_image_size(src, img)
blob = img.to_blob {|e| e.format = "RGB"; e.depth = 8 }
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
glBindTexture(GL_TEXTURE_2D, texture)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
img.columns.to_i, img.rows.to_i, 0, GL_RGB, GL_UNSIGNED_BYTE, blob)
end
def check_image_size(src, img)
if img.columns.to_i != img.rows.to_i
raise "#{src}: image is not square (#{img.columns}, #{img.rows})."
end
x = img.columns.to_i
if x <= 32 or (x & (x - 1)) != 0
raise "#{src}: image size is not power of 2 (#{img.columns})."
end
true
end
def handle_display()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(@fov, WINDOW_WIDTH.to_f / WINDOW_HEIGHT.to_f, 0.5, 100.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
r = Math.cos(@yzphi * Math::PI / 180.0)
x = r * Math.cos(@xztheta * Math::PI / 180.0)
z = r * Math.sin(@xztheta * Math::PI / 180.0)
y = Math.sin(@yzphi * Math::PI / 180.0)
gluLookAt(0.0, 0.0, 0.0, x, y, z, 0, 1, 0)
draw_cube_surfaces()
glFlush()
glutSwapBuffers()
end
# OpenGL 3D coordinates and Cubic Panorama textures
# We set the camera at origin.
#
# ^y (0,0)--(1,0)
# |/ | jpg |
# (-+-)----(-++) ---+--> x | |
# | [4] | /| (0,1)--(1,1)
# | 0,1,0 | z
# | |
# (++-)----(+++)----(-++)----(-+-)----(++-) (vertex)
# | [0] | [1] | [2] | [3] | [texture]
# | 1,0,0 | 0,0,1 | -1,0,0 | 0,0,-1 | camera direction
# | | | | |
# (+--)----(+-+)----(--+)----(---)----(+--)
# | [5] |
# | 0,-1,0 |
# | |
# (---)----(--+)
VERTEX = [
[+1.0, +1.0, -1.0], [+1.0, -1.0, -1.0],
[+1.0, +1.0, +1.0], [+1.0, -1.0, +1.0],
[-1.0, +1.0, +1.0], [-1.0, -1.0, +1.0],
[-1.0, +1.0, -1.0], [-1.0, -1.0, -1.0]
]
def draw_cube_surfaces()
glEnable(GL_TEXTURE_2D)
draw_surface(@texture[0], VERTEX[0], VERTEX[1], VERTEX[3], VERTEX[2])
draw_surface(@texture[1], VERTEX[2], VERTEX[3], VERTEX[5], VERTEX[4])
draw_surface(@texture[2], VERTEX[4], VERTEX[5], VERTEX[7], VERTEX[6])
draw_surface(@texture[3], VERTEX[6], VERTEX[7], VERTEX[1], VERTEX[0])
draw_surface(@texture[4], VERTEX[6], VERTEX[0], VERTEX[2], VERTEX[4])
draw_surface(@texture[5], VERTEX[1], VERTEX[7], VERTEX[5], VERTEX[3])
end
def draw_surface(texture, left_top, left_bottom, right_bottom, right_top)
glBindTexture(GL_TEXTURE_2D, texture)
glBegin(GL_POLYGON)
glTexCoord2f(0.0, 0.0)
glVertex3fv(left_top)
glTexCoord2f(0.0, 1.0)
glVertex3fv(left_bottom)
glTexCoord2f(1.0, 1.0)
glVertex3fv(right_bottom)
glTexCoord2f(1.0, 0.0)
glVertex3fv(right_top)
glEnd()
end
def handle_mouse(button, state, x, y)
if state == 0
@point_start.x, @point_start.y = x, y
@angle_start.x, @angle_start.y = @xztheta, @yzphi
end
end
def handle_motion(x, y)
fac = 0.2
@xztheta = ((@angle_start.x + (x - @point_start.x) * fac) % 360).to_i
phi = (@angle_start.y + (@point_start.y - y) * fac).to_i
if -90 <= phi and phi <= 90
@yzphi = phi
end
glutPostRedisplay()
end
def handle_keypress(key, x, y)
case key
when ?h
rotate_horizontal(-@angle_step)
when ?l
rotate_horizontal(@angle_step)
when ?k
rotate_vertical(@angle_step)
when ?j
rotate_vertical(-@angle_step)
when ?u
reset_angle()
when ?i
zoom(-@fov_step)
when ?o
zoom(@fov_step)
when ?q
exit(0)
end
end
def rotate_horizontal(theta_step)
@xztheta = (@xztheta + theta_step) % 360
glutPostRedisplay()
end
def rotate_vertical(phi_step)
if -90 <= @yzphi + phi_step and @yzphi + phi_step <= +90
@yzphi += phi_step
glutPostRedisplay()
end
end
def reset_angle()
@xztheta = @yzphi = 0
glutPostRedisplay()
end
def zoom(fov_step)
if 40 <= @fov + fov_step and @fov + fov_step <= 80
@fov += fov_step
glutPostRedisplay()
end
end
end
end
if $0 == __FILE__
Panorama::Cubic.new(ARGV.shift).run
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.