-
Notifications
You must be signed in to change notification settings - Fork 0
Wireframe
kkrmno edited this page Sep 18, 2020
·
1 revision
##
#Class representing a point in 3D space.
class Point
attr_reader :x, :y, :z
def initialize(x,y,z)
@x = x
@y = y
@z = z
end
def rotate_x(theta)
ct = Math.cos(theta)
st = Math.sin(theta)
y = @y
z = @z
@y = y*ct - z*st
@z = y*st + z*ct
end
def rotate_y(theta)
ct = Math.cos(theta)
st = Math.sin(theta)
x = @x
z = @z
@x = x*ct + z*st
@z = -x*st + z*ct
end
def rotate_z(theta)
ct = Math.cos(theta)
st = Math.sin(theta)
x = @x
y = @y
@x = x*ct - y*st
@y = x*st + y*ct
end
def translate(x,y,z)
@x += x
@y += y
@z += z
end
def mult(c)
@x *= c
@y *= c
@z *= c
end
end
##
#Class representing a triangle in 3D space. Contains three Point instances.
class Triangle
attr_accessor :color
attr_reader :p0, :p1, :p2
def initialize(x0,y0,z0,x1,y1,z1,x2,y2,z2)
@p0 = Point.new(x0,y0,z0)
@p1 = Point.new(x1,y1,z1)
@p2 = Point.new(x2,y2,z2)
@color = 255
@points = [@p0, @p1, @p2]
end
def translate(x,y,z)
@points.each{|pt| pt.translate(x,y,z)}
end
def rotate_x(theta)
@points.each{|pt| pt.rotate_x(theta)}
end
def rotate_y(theta)
@points.each{|pt| pt.rotate_y(theta)}
end
def rotate_z(theta)
@points.each{|pt| pt.rotate_z(theta)}
end
def average_depth
z0 = @p0.z
z1 = @p1.z
z2 = @p2.z
[z0,z1,z2].inject(:+)/3.0
end
def render(img)
thickness = 2 * @color/255
img.draw_line!(@p0.x, @p0.y, @p1.x, @p1.y, @color, thickness)
img.draw_line!(@p1.x, @p1.y, @p2.x, @p2.y, @color, thickness)
img.draw_line!(@p2.x, @p2.y, @p0.x, @p0.y, @color, thickness)
end
def scale(c)
@points.each{|pt| pt.mult(c)}
end
end
##
#Class representing a 3D mesh made up of triangles.
class Mesh
def initialize(triangles, size)
@triangles = triangles
@triangles.each{|t| t.scale(size)}
end
def rotate_x(theta)
@triangles.each{|t| t.rotate_x(theta)}
end
def rotate_y(theta)
@triangles.each{|t| t.rotate_y(theta)}
end
def rotate_z(theta)
@triangles.each{|t| t.rotate_z(theta)}
end
def translate(x,y,z)
@triangles.each{|t| t.translate(x,y,z)}
end
def render(img)
@triangles.sort_by!{|tri| tri.average_depth}
@triangles.each{|t| img = t.render(img)}
img
end
end
##
#Rudimentary method for reading obj-files. Only limited support.
def read_obj_file(path)
vertices = []
faces = []
triangles = []
File.open(path, "r") do |f|
f.each_line do |line|
line = line.strip!
type = line[0]
next if type == "#"
line_ = line.split(" ")
type = line_[0]
data = line_[1..-1]
if type == "v"
vertices << data.collect{|v| v.to_f}[0..2]
elsif type == "f"
faces << data.collect{|f| f_ = f.to_i; if f_ > 0 then f_ - 1 else f_ end}
end
end
end
faces.each do |face|
next if face.size != 3
p0 = vertices[face[0]]
p1 = vertices[face[1]]
p2 = vertices[face[2]]
ps = [p0,p1,p2].flatten
triangles << Triangle.new(*ps)
end
triangles
end
##
#Sets colors of triangles based on their z-position
def update_colors(triangles, min_depth, max_depth)
triangles.each do |t|
d = t.average_depth
col = (d - min_depth) / max_depth
col = [0.01, col].max
col = [1.0, col].min
col = 1-col
col = col*255
t.color = col
end
end
#Animated image containing the rendered image.
image_width = 95
image_height = 75
img = Imgrb::Image.new(image_width,image_height,255)
#Read obj-file and create Mesh instance.
path = "teapot.obj"
tris = read_obj_file(path)
sz = 0.5
mesh = Mesh.new(tris, sz)
#Set "camera" to desired position
x_offset = 0
y_offset = -10
z_offset = 0
mesh.rotate_x(Math::PI/2)
x_min = Float::INFINITY
x_max = -Float::INFINITY
y_min = Float::INFINITY
y_max = -Float::INFINITY
z_min = Float::INFINITY
z_max = -Float::INFINITY
tris.each do |tri|
points = [tri.p0, tri.p1, tri.p2]
points.each do |p|
x_min = p.x < x_min ? p.x : x_min
x_max = p.x > x_max ? p.x : x_max
y_min = p.y < y_min ? p.y : y_min
y_max = p.y > y_max ? p.y : y_max
z_min = p.z < z_min ? p.z : z_min
z_max = p.z > z_max ? p.z : z_max
end
end
x_min += x_offset
x_max += x_offset
y_min += y_offset
y_max += y_offset
z_min += z_offset
z_max += z_offset
mesh.translate(sz-x_min,sz-y_min,sz-z_min)
#Set colors of triangles based on z-position (fade out far from camera)
min_depth = Float::INFINITY
max_depth = -Float::INFINITY
tris.each do |t|
d = t.average_depth
min_depth = d if d < min_depth
max_depth = d if d > max_depth
end
update_colors(tris, min_depth, max_depth)
#Render initial image
mesh.render(img)
mesh.translate(-sz+x_min,-sz+y_min,-sz+z_min)
#Render rotations of the object
214.times do
mesh.rotate_x(Math::PI/120)
mesh.rotate_z(Math::PI/240)
mesh.translate(sz-x_min,sz-y_min,sz-z_min)
#Canvas for next frame:
img_ = Imgrb::Image.new(image_width,image_height,255)
update_colors(tris, min_depth, max_depth)
mesh.render(img_)
mesh.translate(-sz+x_min,-sz+y_min,-sz+z_min)
img.push_frame(img_, 0, 0, 1, 60)
end
img.add_comment("Object: #{path}")
img.add_comment("Animation created by Imgrb.")
img.save("teapot.png")