Skip to content

Instantly share code, notes, and snippets.

@silverhammermba
Created February 25, 2014 04:02
Show Gist options
  • Save silverhammermba/9202455 to your computer and use it in GitHub Desktop.
Save silverhammermba/9202455 to your computer and use it in GitHub Desktop.
Mobius Ring Generator
#!/usr/bin/env ruby
# generate OBJ of Mobius wedding band
require 'matrix'
if ARGV.size != 6
STDERR.puts "usage: #$0 SIDES RADIUS SAMPLES STRETCH TURNS CURVE"
exit 1
end
# sides: sides of mobius prism
# radius: radius of ring (prism is fixed at unit radius)
# samples: number of steps in the rotation around the band
# stretch: how much to squish the shape perpendicular to the axis of rotation
# turns: number of turns made, e.g. 0 for normal prism, 1 for normal mobius band
# curve: curvature approximated by normals e.g. 0 for prism, 1 for cylinder
$sides = ARGV[0].to_i
$radius = ARGV[1].to_f
$samples = ARGV[2].to_i
$stretch = ARGV[3].to_f
$turns = ARGV[4].to_i
$curve = ARGV[5].to_f
def rotate_y rad
Matrix[[Math.cos(rad), 0, -Math.sin(rad)], [0, 1, 0], [Math.sin(rad), 0, Math.cos(rad)]].transpose
end
def rotate_z rad
Matrix[[Math.cos(rad), Math.sin(rad), 0], [-Math.sin(rad), Math.cos(rad), 0], [0, 0, 1]].transpose
end
normals = []
samples = $samples.times.map do |sample|
rotz = rotate_z((2 * Math::PI * sample) / $samples)
# normals for this sample
norms = []
samp = $sides.times.map do |side|
[-1, 1].each do |dir|
norm = Vector[1, 0, 0]
# rotate norm according to current side, current direction, desired curvature, and current sample
norm = rotate_y((2 * Math::PI * side) / $sides + (Math::PI * dir * (1 - $curve)) / $sides + (2 * Math::PI * sample * $turns) / ($sides * $samples)) * norm
# scale by inverse transpose and renormalize
norm = Vector[norm[0] / $stretch, norm[1], norm[2]].normalize
# rotate to sample position
norm = rotz * norm
# format
norms << norm.to_a.map { |f| f.round(6) }
end
point = Vector[1, 0, 0]
# rotate point about Y according to current side and current sample
point = rotate_y((2 * Math::PI * side) / $sides + (2 * Math::PI * sample * $turns) / ($sides * $samples)) * point
# scale by X
point = Vector[point[0] * $stretch, point[1], point[2]]
# translate by radius
point = point + Vector[$radius, 0, 0]
# rotate to sample position
point = rotz * point
# format
point.to_a.map { |f| f.round(6) }
end
normals << norms
samp
end
# output samples
samples.each do |sample|
sample.each do |point|
puts "v #{point.join(' ')}"
end
end
# output normals
normals.each do |norms|
norms.each do |normal|
puts "vn #{normal.join(' ')}"
end
end
# get normal index from sample, vertex, direction
def v2n s, v, d
# offset of normals + 2 norms per side + negative dir first
$samples * $sides + 1 + s * $sides * 2 + v * 2 + (d > 0 ? 1 : 0)
end
# output faces (last connection is different)
(0...($samples - 1)).each do |i|
i1 = (i + 1) % $samples
samples[i].each_with_index do |point, j|
j1 = (j + 1) % $sides
puts "f #{i * $sides + j + 1}//#{v2n(i, j, 1)} #{i1 * $sides + j1 + 1}//#{v2n(i1, j1, -1)} #{i1 * $sides + j + 1}//#{v2n(i1, j, 1)}"
puts "f #{i * $sides + j + 1}//#{v2n(i, j, 1)} #{i * $sides + j1 + 1}//#{v2n(i, j1, -1)} #{i1 * $sides + j1 + 1}//#{v2n(i1, j1, -1)}"
end
end
# output last faces
k = ($samples - 1) * $sides
$sides.times do |j|
cj = (j - $turns) % $sides
nj = (j + 1) % $sides
puts "f #{k + cj + 1}//#{v2n($samples - 1, cj, 1)} #{nj + 1}//#{v2n(0, nj, -1)} #{j + 1}//#{v2n(0, j, 1)}"
puts "f #{k + cj + 1}//#{v2n($samples - 1, cj, 1)} #{k + (j - $turns + 1) % $sides + 1}//#{v2n($samples - 1, (j - $turns + 1) % $sides, -1)} #{nj + 1}//#{v2n(0, nj, -1)}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment