Skip to content

Instantly share code, notes, and snippets.

@hakanai
Last active January 31, 2018 05:33
Show Gist options
  • Save hakanai/f78b211147ff524c7bd2f565a6e1e205 to your computer and use it in GitHub Desktop.
Save hakanai/f78b211147ff524c7bd2f565a6e1e205 to your computer and use it in GitHub Desktop.
Quick and dirty .obj/.mtl generation in Ruby
#!/usr/bin/env ruby
require_relative 'obj'
obj = Obj.build('cube') do
# More about material properties here:
# https://en.wikipedia.org/wiki/Wavefront_.obj_file
# http://www.fileformat.info/format/material/
material = material('cube',
'Ns' => '10.0000', # specular exponent (0 - 1000)
'Ni' => '1.5000', # illumination exponent?
'd' => '1.0000', # opacity (0.0 = transparent, 1.0 = opaque)
'Tr' => '0.0000', # transparency (0.0 = opaque, 1.0 = transparent)
'Tf' => '1.0000 1.0000 1.0000', # transmission filter (r,g,b)
'illum' => '2', # illumination model
# 0. Color on and Ambient off
# 1. Color on and Ambient on
# 2. Highlight on
# 3. Reflection on and Ray trace on
# 4. Transparency: Glass on, Reflection: Ray trace on
# 5. Reflection: Fresnel on and Ray trace on
# 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
# 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
# 8. Reflection on and Ray trace off
# 9. Transparency: Glass on, Reflection: Ray trace off
# 10. Casts shadows onto invisible surfaces
'Ka' => '0.0000 0.0000 0.0000', # ambient colour (r,g,b)
'Kd' => '0.5880 0.5880 0.5880', # diffuse colour (r,g,b)
'Ks' => '0.0000 0.0000 0.0000', # specular colour (r,g,b)
'Ke' => '0.0000 0.0000 0.0000', # emissive colour (r,g,b)
'map_Ka' => 'cube.png', # ambient colour texture map
'map_Kd' => 'cube.png') # diffuse colour texture map
# map_Ks = specular colour texture map
# map_Ns = specular highlight component
# map_d = alpha texture map
# map_bump / bump = bump map
# disp = displacement map
# decal = stencil decal texture
# refl = spherical reflection map
v1 = vertex(-0.5, -0.5, 0.5)
v2 = vertex( 0.5, -0.5, 0.5)
v3 = vertex(-0.5, 0.5, 0.5)
v4 = vertex( 0.5, 0.5, 0.5)
v5 = vertex(-0.5, 0.5, -0.5)
v6 = vertex( 0.5, 0.5, -0.5)
v7 = vertex(-0.5, -0.5, -0.5)
v8 = vertex( 0.5, -0.5, -0.5)
vt1 = vertex_texture(0.0, 0.0)
vt2 = vertex_texture(1.0, 0.0)
vt3 = vertex_texture(0.0, 1.0)
vt4 = vertex_texture(1.0, 1.0)
vn1 = vertex_normal( 0.0, 0.0, 1.0)
vn2 = vertex_normal( 0.0, 1.0, 0.0)
vn3 = vertex_normal( 0.0, 0.0, -1.0)
vn4 = vertex_normal( 0.0, -1.0, 0.0)
vn5 = vertex_normal( 1.0, 0.0, 0.0)
vn6 = vertex_normal(-1.0, 0.0, 0.0)
group('cube', material) do |group|
group.face([v1,vt1,vn1], [v2,vt2,vn1], [v4,vt4,vn1], [v3,vt3,vn1])
group.face([v3,vt1,vn2], [v4,vt2,vn2], [v6,vt4,vn2], [v5,vt3,vn2])
group.face([v5,vt4,vn3], [v6,vt3,vn3], [v8,vt1,vn3], [v7,vt2,vn3])
group.face([v7,vt1,vn4], [v8,vt2,vn4], [v2,vt4,vn4], [v1,vt3,vn4])
group.face([v2,vt1,vn5], [v8,vt2,vn5], [v6,vt4,vn5], [v4,vt3,vn5])
group.face([v7,vt1,vn6], [v1,vt2,vn6], [v3,vt4,vn6], [v5,vt3,vn6])
end
end
obj.save('cube')
#!/usr/bin/env ruby
require_relative 'obj'
obj = Obj.build('hair_accessory') do
# Materials might be completely wrong because nothing I have here seems to render materials.
frame_material = material('frame',
'Ns' => '10.0000', # specular exponent (0 - 1000)
'Ni' => '1.5000', # illumination exponent?
'illum' => '2',
'Ka' => '0.0 0.0 0.0',
'Kd' => '0.0 0.0 0.0',
'Ks' => '0.0 0.0 0.0',
'Ke' => '0.0 0.0 0.0')
glow_material = material('glow',
'Ns' => '10.0000', # specular exponent (0 - 1000)
'Ni' => '1.5000', # illumination exponent?
'illum' => '2',
'Ka' => '0.0 0.0 0.0',
'Kd' => '1.0 0.0 0.25',
'Ks' => '0.0 0.0 0.0',
'Ke' => '1.0 0.0 0.25')
inner_width = 8
inner_height = 8
outer_width = 10
outer_height = 10
slice_thickness = 0.4
glow_inset = 0.2
inset_width = outer_width - glow_inset * 2
inset_height = outer_height - glow_inset * 2
z_front = slice_thickness * 3 / 2
z_back = -z_front
z_front_rim = slice_thickness / 2
z_back_rim = -z_front_rim
v_front1 = vertex(-outer_width/2, outer_height/2, z_front)
v_front2 = vertex( outer_width/2, outer_height/2, z_front)
v_front3 = vertex(-outer_width/2, inner_height/2, z_front)
v_front4 = vertex(-inner_width/2, inner_height/2, z_front)
v_front5 = vertex( inner_width/2, inner_height/2, z_front)
v_front6 = vertex( outer_width/2, inner_height/2, z_front)
v_front7 = vertex(-outer_width/2, -inner_height/2, z_front)
v_front8 = vertex(-inner_width/2, -inner_height/2, z_front)
v_front9 = vertex( inner_width/2, -inner_height/2, z_front)
v_front10 = vertex( outer_width/2, -inner_height/2, z_front)
v_front11 = vertex(-outer_width/2, -outer_height/2, z_front)
v_front12 = vertex( outer_width/2, -outer_height/2, z_front)
v_back1 = vertex(-outer_width/2, outer_height/2, z_back)
v_back2 = vertex( outer_width/2, outer_height/2, z_back)
v_back3 = vertex(-outer_width/2, inner_height/2, z_back)
v_back4 = vertex(-inner_width/2, inner_height/2, z_back)
v_back5 = vertex( inner_width/2, inner_height/2, z_back)
v_back6 = vertex( outer_width/2, inner_height/2, z_back)
v_back7 = vertex(-outer_width/2, -inner_height/2, z_back)
v_back8 = vertex(-inner_width/2, -inner_height/2, z_back)
v_back9 = vertex( inner_width/2, -inner_height/2, z_back)
v_back10 = vertex( outer_width/2, -inner_height/2, z_back)
v_back11 = vertex(-outer_width/2, -outer_height/2, z_back)
v_back12 = vertex( outer_width/2, -outer_height/2, z_back)
v_front_rim1 = vertex(-outer_width/2, outer_height/2, z_front_rim)
v_front_rim2 = vertex( outer_width/2, outer_height/2, z_front_rim)
v_front_rim3 = vertex(-outer_width/2, inset_height/2, z_front_rim)
v_front_rim4 = vertex(-inset_width/2, inset_height/2, z_front_rim)
v_front_rim5 = vertex( inset_width/2, inset_height/2, z_front_rim)
v_front_rim6 = vertex( outer_width/2, inset_height/2, z_front_rim)
v_front_rim7 = vertex(-outer_width/2, -inset_height/2, z_front_rim)
v_front_rim8 = vertex(-inset_width/2, -inset_height/2, z_front_rim)
v_front_rim9 = vertex( inset_width/2, -inset_height/2, z_front_rim)
v_front_rim10 = vertex( outer_width/2, -inset_height/2, z_front_rim)
v_front_rim11 = vertex(-outer_width/2, -outer_height/2, z_front_rim)
v_front_rim12 = vertex( outer_width/2, -outer_height/2, z_front_rim)
v_back_rim1 = vertex(-outer_width/2, outer_height/2, z_back_rim)
v_back_rim2 = vertex( outer_width/2, outer_height/2, z_back_rim)
v_back_rim3 = vertex(-outer_width/2, inset_height/2, z_back_rim)
v_back_rim4 = vertex(-inset_width/2, inset_height/2, z_back_rim)
v_back_rim5 = vertex( inset_width/2, inset_height/2, z_back_rim)
v_back_rim6 = vertex( outer_width/2, inset_height/2, z_back_rim)
v_back_rim7 = vertex(-outer_width/2, -inset_height/2, z_back_rim)
v_back_rim8 = vertex(-inset_width/2, -inset_height/2, z_back_rim)
v_back_rim9 = vertex( inset_width/2, -inset_height/2, z_back_rim)
v_back_rim10 = vertex( outer_width/2, -inset_height/2, z_back_rim)
v_back_rim11 = vertex(-outer_width/2, -outer_height/2, z_back_rim)
v_back_rim12 = vertex( outer_width/2, -outer_height/2, z_back_rim)
vt1 = vertex_texture(0.0, 0.0)
vt2 = vertex_texture(1.0, 0.0)
vt3 = vertex_texture(0.0, 1.0)
vt4 = vertex_texture(1.0, 1.0)
vn1 = vertex_normal( 0.0, 0.0, 1.0)
vn2 = vertex_normal( 0.0, 1.0, 0.0)
vn3 = vertex_normal( 0.0, 0.0, -1.0)
vn4 = vertex_normal( 0.0, -1.0, 0.0)
vn5 = vertex_normal( 1.0, 0.0, 0.0)
vn6 = vertex_normal(-1.0, 0.0, 0.0)
group('frame', frame_material) do
# facing forwards
face([v_front2, vt1,vn1], [v_front1, vt2,vn1], [v_front3, vt4,vn1], [v_front6, vt3,vn1]) # front face, top panel
face([v_front10, vt1,vn1], [v_front7, vt2,vn1], [v_front11, vt4,vn1], [v_front12, vt3,vn1]) # front face, bottom panel
face([v_front4, vt1,vn1], [v_front3, vt2,vn1], [v_front7, vt4,vn1], [v_front8, vt3,vn1]) # front face, left panel
face([v_front6, vt1,vn1], [v_front5, vt2,vn1], [v_front9, vt4,vn1], [v_front10, vt3,vn1]) # front face, right panel
face([v_back_rim2, vt1,vn1], [v_back_rim1, vt2,vn1], [v_back_rim3, vt4,vn1], [v_back_rim6, vt3,vn1]) # back of inset, top panel
face([v_back_rim10, vt1,vn1], [v_back_rim7, vt2,vn1], [v_back_rim11, vt4,vn1], [v_back_rim12, vt3,vn1]) # back of inset, bottom panel
face([v_back_rim4, vt1,vn1], [v_back_rim3, vt2,vn1], [v_back_rim7, vt4,vn1], [v_back_rim8, vt3,vn1]) # back of inset, left panel
face([v_back_rim6, vt1,vn1], [v_back_rim5, vt2,vn1], [v_back_rim9, vt4,vn1], [v_back_rim10, vt3,vn1]) # back of inset, right panel
# facing backwards
face([v_back1, vt1,vn3], [v_back2, vt2,vn3], [v_back6, vt4,vn3], [v_back3, vt3,vn3]) # back face, top panel
face([v_back7, vt1,vn3], [v_back10, vt2,vn3], [v_back12, vt4,vn3], [v_back11, vt3,vn3]) # back face, bottom panel
face([v_back3, vt1,vn3], [v_back4, vt2,vn3], [v_back8, vt4,vn3], [v_back7, vt3,vn3]) # back face, left panel
face([v_back5, vt1,vn3], [v_back6, vt2,vn3], [v_back10, vt4,vn3], [v_back9, vt3,vn3]) # back face, right panel
face([v_front_rim1, vt1,vn3], [v_front_rim2, vt2,vn3], [v_front_rim6, vt4,vn3], [v_front_rim3, vt3,vn3]) # front of inset, top panel
face([v_front_rim7, vt1,vn3], [v_front_rim10,vt2,vn3], [v_front_rim12, vt4,vn3], [v_front_rim11, vt3,vn3]) # front of inset, bottom panel
face([v_front_rim3, vt1,vn3], [v_front_rim4, vt2,vn3], [v_front_rim8, vt4,vn3], [v_front_rim7, vt3,vn3]) # front of inset, left panel
face([v_front_rim5, vt1,vn3], [v_front_rim6, vt2,vn3], [v_front_rim10, vt4,vn3], [v_front_rim9, vt3,vn3]) # front of inset, right panel
# facing upwards
face([v_front1, vt1,vn2], [v_front2, vt2,vn2], [v_front_rim2, vt3,vn2], [v_front_rim1, vt4,vn2]) # top edge, front rim
face([v_back_rim1, vt1,vn2], [v_back_rim2, vt2,vn2], [v_back2, vt3,vn2], [v_back1, vt4,vn2]) # top edge, back rim
face([v_front8, vt1,vn2], [v_front9, vt2,vn2], [v_back9, vt3,vn2], [v_back8, vt4,vn2]) # inside edge, bottom
# facing downwards
face([v_front12, vt1,vn4], [v_front11, vt2,vn4], [v_front_rim11, vt3,vn4], [v_front_rim12, vt4,vn4]) # bottom edge, front rim
face([v_back_rim12, vt1,vn4], [v_back_rim11, vt2,vn4], [v_back11, vt3,vn4], [v_back12, vt4,vn4]) # bottom edge, back rim
face([v_front5, vt1,vn4], [v_front4, vt2,vn4], [v_back4, vt3,vn4], [v_back5, vt4,vn4]) # inside edge, top
# facing leftwards
face([v_front1, vt1,vn6], [v_front_rim1, vt2,vn6], [v_front_rim11, vt3,vn6], [v_front11, vt2,vn6]) # left edge, front rim
face([v_back_rim1, vt1,vn6], [v_back1, vt2,vn6], [v_back11, vt3,vn6], [v_back_rim11, vt2,vn6]) # left edge, front rim
face([v_back4, vt1,vn6], [v_front4, vt2,vn6], [v_front8, vt3,vn6], [v_back8, vt2,vn6]) # inside edge, left
# facing rightwards
face([v_front_rim2, vt1,vn5], [v_front2, vt2,vn5], [v_front12, vt3,vn5], [v_front_rim12, vt2,vn5]) # right edge, front rim
face([v_back2, vt1,vn5], [v_back_rim2, vt2,vn5], [v_back_rim12, vt3,vn5], [v_back12, vt2,vn5]) # right edge, front rim
face([v_front5, vt1,vn5], [v_back5, vt2,vn5], [v_back9, vt3,vn5], [v_front9, vt2,vn5]) # inside edge, left
end
group('glow', glow_material) do
face([v_back_rim5, vt1,vn2], [v_back_rim4, vt2,vn2], [v_front_rim4, vt3,vn2], [v_front_rim5, vt4,vn2]) # top
face([v_front_rim9, vt1,vn4], [v_front_rim8, vt2,vn4], [v_back_rim8, vt3,vn4], [v_back_rim9, vt4,vn4]) # bottom
face([v_front_rim8, vt1,vn6], [v_front_rim4, vt2,vn6], [v_back_rim4, vt3,vn6], [v_back_rim8, vt4,vn6]) # left
face([v_back_rim9, vt1,vn5], [v_back_rim5, vt2,vn5], [v_front_rim5, vt3,vn5], [v_front_rim9, vt4,vn5]) # right
end
end
obj.save('hair_accessory')
#!/usr/bin/env ruby
require_relative 'obj'
['Mask', 'Material'].each do |type|
obj = Obj.build("Stencil#{type}Scanner") do
frame_material = material('StencilScannerFrame',
'Ns' => '10.0000',
'Ni' => '1.5000',
'illum' => '2',
'Ka' => '0.0 0.0 0.0',
'Kd' => '0.0 0.0 0.0',
'Ks' => '0.0 0.0 0.0',
'Ke' => '0.0 0.0 0.0')
pane_materials = (0..255).map do |i|
material("StencilScanner#{type}#{i}",
'Ns' => '10.0000',
'Ni' => '1.5000',
'd' => 0.0,
'Tr' => 1.0,
'illum' => '4',
'Ka' => '0.0 0.0 0.0',
'Kd' => '0.9 0.9 0.9',
'Ks' => '0.0 0.0 0.0',
'Ke' => '0.0 0.0 0.0',
'Tf' => '1.0 1.0 1.0',
'map_Ka' => '../Textures/StencilScannerGrid.png',
'map_Kd' => '../Textures/StencilScannerGrid.png')
end
pane_size = 0.01
frame_thickness = 0.002
vertex_holder = self
vt = (0..16).map{|i| (0..16).map{|j| vertex_texture(i/16.0, (16-j)/16.0)}}
vn_front = vertex_normal( 0.0, 0.0, 1.0)
vn_back = vertex_normal( 0.0, 0.0, -1.0)
vn_right = vertex_normal( 1.0, 0.0, 0.0)
vn_left = vertex_normal(-1.0, 0.0, 0.0)
vn_top = vertex_normal( 0.0, 1.0, 0.0)
vn_bottom = vertex_normal( 0.0, -1.0, 0.0)
# panes
(0..15).each do |row|
(0..15).each do |column|
pane_number = row * 16 + column
group("StencilScannerPane#{pane_number}", pane_materials[pane_number]) do
pane_x1 = pane_size * column
pane_x2 = pane_size * (column + 1)
pane_y1 = pane_size * (15 - row)
pane_y2 = pane_size * (15 - row + 1)
pane_v1 = vertex_holder.vertex(pane_x1, pane_y1, 0)
pane_v2 = vertex_holder.vertex(pane_x1, pane_y2, 0)
pane_v3 = vertex_holder.vertex(pane_x2, pane_y2, 0)
pane_v4 = vertex_holder.vertex(pane_x2, pane_y1, 0)
vt1 = vt[column][row+1]
vt2 = vt[column][row]
vt3 = vt[column+1][row]
vt4 = vt[column+1][row+1]
face([pane_v1,vt1,vn_front], [pane_v2,vt2,vn_front], [pane_v3,vt3,vn_front], [pane_v4,vt4,vn_front])
face([pane_v4,vt4,vn_back], [pane_v3,vt3,vn_back], [pane_v2,vt2,vn_back], [pane_v1,vt1,vn_back])
end
end
end
# frame
group('StencilScannerFrame', frame_material) do
frame_x = (0..16).map{|column| pane_size * column}
frame_y = (0..16).map{|row| pane_size * row}
half_frame_thickness = frame_thickness/2
self.define_singleton_method(:cube) do |x1, x2, y1, y2, z1, z2|
left_bottom_back = vertex_holder.vertex(x1, y1, z1)
left_bottom_front = vertex_holder.vertex(x1, y1, z2)
left_top_back = vertex_holder.vertex(x1, y2, z1)
left_top_front = vertex_holder.vertex(x1, y2, z2)
right_bottom_back = vertex_holder.vertex(x2, y1, z1)
right_bottom_front = vertex_holder.vertex(x2, y1, z2)
right_top_back = vertex_holder.vertex(x2, y2, z1)
right_top_front = vertex_holder.vertex(x2, y2, z2)
vt1 = vt2 = vt3 = vt4 = vt[0][0]
face([left_bottom_front, vt1,vn_front ], [right_bottom_front,vt2,vn_front ], [right_top_front, vt3,vn_front ], [left_top_front, vt4,vn_front ])
face([right_bottom_back, vt1,vn_back ], [left_bottom_back, vt2,vn_back ], [left_top_back, vt3,vn_back ], [right_top_back, vt4,vn_back ])
face([right_bottom_front,vt1,vn_right ], [right_bottom_back, vt2,vn_right ], [right_top_back, vt3,vn_right ], [right_top_front, vt4,vn_right ])
face([left_bottom_back ,vt1,vn_left ], [left_bottom_front, vt2,vn_left ], [left_top_front, vt3,vn_left ], [left_top_back, vt4,vn_left ])
face([left_top_front ,vt1,vn_top ], [right_top_front ,vt2,vn_top ], [right_top_back, vt3,vn_top ], [left_top_back, vt4,vn_top ])
face([right_bottom_front,vt1,vn_bottom], [left_bottom_front ,vt2,vn_bottom], [left_bottom_back,vt3,vn_bottom], [right_bottom_back,vt4,vn_bottom])
end
frame_x.each do |x|
cube(x-half_frame_thickness, x+half_frame_thickness,
frame_y[0]-half_frame_thickness, frame_y[16]+half_frame_thickness,
-half_frame_thickness, half_frame_thickness)
end
frame_y.each do |y|
cube(frame_x[0]-half_frame_thickness, frame_x[16]+half_frame_thickness,
y-half_frame_thickness, y+half_frame_thickness,
-half_frame_thickness, half_frame_thickness)
end
end
end
obj.save("Stencil#{type}Scanner")
end
class Obj
attr_reader :object_name
# name => material info
attr_reader :materials
attr_reader :vertices
attr_reader :vertex_textures
attr_reader :vertex_normals
attr_reader :groups
def initialize(object_name)
@object_name = object_name
@materials = {}
@vertices = []
@vertex_textures = []
@vertex_normals = []
@groups = []
end
def self.build(object_name, &block)
obj = Obj.new(object_name)
obj.instance_eval(&block)
obj
end
def material(name, material_info)
@materials[name] = material_info
name
end
def vertex(x, y, z)
@vertices << [x,y,z]
@vertices.size
end
def vertex_texture(u, v)
@vertex_textures << [u, v]
@vertex_textures.size
end
def vertex_normal(x, y, z)
@vertex_normals << [x, y, z]
@vertex_normals.size
end
def group(name, material_name, &block)
group = Group.new(name, material_name)
@groups << group
group.instance_eval(&block)
end
def save(base_path)
obj_path = base_path + '.obj'
mtl_path = base_path + '.mtl'
mtl_name = File.basename(mtl_path)
File.open(obj_path, 'w') do |io|
io.puts("o #{@object_name}")
io.puts("mtllib #{mtl_name}")
@vertices.each do |x,y,z|
io.puts("v #{x} #{y} #{z}")
end
@vertex_textures.each do |u,v|
io.puts("vt #{u} #{v}")
end
@vertex_normals.each do |x,y,z|
io.puts("vn #{x} #{y} #{z}")
end
@groups.each do |group|
io.puts("g #{group.name}")
io.puts("usemtl #{group.material_name}")
group.smoothing_groups.each_with_index do |smoothing_group, index|
io.puts("s #{index + 1}")
smoothing_group.faces.each do |face_info|
face_info_str = face_info.map{|el| el.join('/')}.join(' ')
io.puts("f #{face_info_str}")
end
end
end
end
File.open(mtl_path, 'w') do |io|
@materials.each_pair do |name, material_info|
io.puts("newmtl #{name}")
material_info.each_pair do |key, value|
io.puts(" #{key} #{value}")
end
end
end
end
class Group
attr_reader :name
attr_reader :material_name
attr_reader :smoothing_groups
def initialize(name, material_name)
@name = name
@material_name = material_name
@smoothing_groups = []
end
def smoothing_group(&block)
smoothing_group = SmoothingGroup.new
@smoothing_groups << smoothing_group
smoothing_group.instance_eval(&block)
end
# Convenience method allowing omitting the explicit call to `smoothing_group` when there is only one face in the group.
# Automatically creates a smoothing group for the single face.
def face(*face_info)
smoothing_group do
face(*face_info)
end
end
end
class SmoothingGroup
attr_reader :faces
def initialize
@faces = []
end
def face(*face_info)
@faces << face_info
nil
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment