Skip to content

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")