Class: Engine::ObjFile

Inherits:
Object
  • Object
show all
Defined in:
lib/engine/importers/obj_file.rb

Instance Method Summary collapse

Constructor Details

#initialize(file_path) ⇒ ObjFile

Returns a new instance of ObjFile.



5
6
7
8
9
10
11
12
# File 'lib/engine/importers/obj_file.rb', line 5

def initialize(file_path)
  @mesh_data = File.readlines(file_path + ".obj")
  if File.exist?(file_path + ".mtl")
    @mtl_data = File.readlines(file_path + ".mtl").map(&:chomp)
  else
    @mtl_data = []
  end
end

Instance Method Details

#decompose_path(path, reverse) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/engine/importers/obj_file.rb', line 102

def decompose_path(path, reverse)
  decomposed_vertices = []
  depth = 100
  until path.length == 3 || depth == 0
    depth -= 1
    ear_result = path.find_ear
    ear = ear_result.first
    new_path = ear_result.last
    ear = ear.reverse! if reverse
    ear_area = triangle_area(ear[0], ear[1], ear[2])
    decomposed_vertices << ear unless ear_area < 0.0001
    path = new_path
  end
  if reverse
    decomposed_vertices << path.points.reverse
  else
    decomposed_vertices << path.points
  end
  decomposed_vertices.map do |triangle|
    "f #{triangle.map { |v| v[:point_string] }.join(" ")}"
  end
end

#face_linesObject



61
62
63
64
65
66
# File 'lib/engine/importers/obj_file.rb', line 61

def face_lines
  @face_lines ||=
    begin
      split_faces(@mesh_data).select { |line| line.start_with?("f ") }
    end
end

#index_dataObject



240
241
242
# File 'lib/engine/importers/obj_file.rb', line 240

def index_data
  @index_data ||= (0...vertex_indices.length).map(&:to_i)
end

#material_for_line(line_number) ⇒ Object



178
179
180
181
182
183
184
185
186
# File 'lib/engine/importers/obj_file.rb', line 178

def material_for_line(line_number)
  l = line_number
  until l == 0
    if @mesh_data[l].start_with?("usemtl ")
      return @mesh_data[l].split(" ")[1]
    end
    l -= 1
  end
end

#material_namesObject



188
189
190
# File 'lib/engine/importers/obj_file.rb', line 188

def material_names
  @material_names ||= []
end

#materialsObject



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/engine/importers/obj_file.rb', line 192

def materials
  @materials ||=
    begin
      current_material = nil
      @mtl_data.each_with_object({}) do |line, materials|
        if line.start_with?("newmtl ")
          _, material_name = line.split(" ")
          current_material = material_name
          materials[material_name] = {}
        elsif line.start_with?("Kd ")
          _, r, g, b = line.split(" ")
          materials[current_material][:diffuse] = Vector[r.to_f, g.to_f, b.to_f]
        elsif line.start_with?("Ks ")
          _, r, g, b = line.split(" ")
          materials[current_material][:specular] = Vector[r.to_f, g.to_f, b.to_f]
        elsif line.start_with?("Ka ")
          _, r, g, b = line.split(" ")
          materials[current_material][:albedo] = Vector[r.to_f, g.to_f, b.to_f]
        end
      end
    end
end

#normal_indicesObject



168
169
170
171
172
173
174
175
# File 'lib/engine/importers/obj_file.rb', line 168

def normal_indices
  @normal_indices ||=
    face_lines.map do |line|
      _, v1, v2, v3 = line.split(" ")
      v1, v2, v3 = [v1, v2, v3].map { |v| v.split("/")[2].to_i }
      [v1 - 1, v2 - 1, v3 - 1]
    end.flatten
end

#normalsObject



22
23
24
25
26
27
28
# File 'lib/engine/importers/obj_file.rb', line 22

def normals
  @normals ||=
    @mesh_data.select { |line| line.start_with?("vn ") }.map do |line|
      _, x, y, z = line.split(" ")
      Vector[x.to_f, y.to_f, z.to_f]
    end
end

#plane_matrix(points) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/engine/importers/obj_file.rb', line 129

def plane_matrix(points)
  raise "Not enough points to create a plane" if points.length < 3

  origin_point = points[0]
  a = points[1] - origin_point
  b = nil
  points[2..-1].each do |point|
    diff = point - origin_point
    b = diff if a.cross(diff).magnitude > 0.0001
  end
  raise "Points are collinear" if b.nil?

  normal = a.cross(b).normalize

  Matrix[
    [a[0], a[1], a[2]],
    [b[0], b[1], b[2]],
    [normal[0], normal[1], normal[2]]
  ].transpose.inverse
end

#split_face(face) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/engine/importers/obj_file.rb', line 82

def split_face(face)
  face_vertices = face.split(" ")[1..-1]
  return [face] if face_vertices.length == 3

  world_points = face_vertices.map { |v| v.split("/")[0] }
          .map { |i| vertices[i.to_i - 1] }
  plane = plane_matrix(world_points)
  flat_points = world_points.map { |point| plane * point }
  packed_points = face_vertices.map.with_index do |v, i|
    { point_string: face_vertices[i], 0 => flat_points[i][0], 1 => flat_points[i][1] }
  end

  path = Path.new(packed_points.reverse)
  begin
    decompose_path(path, true)
  rescue NoEarsException
    decompose_path(Path.new(packed_points), false)
  end
end

#split_faces(lines) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/engine/importers/obj_file.rb', line 68

def split_faces(lines)
  lines.map.with_index do |line, line_number|
    if line.start_with?("f ")
      new_faces = split_face(line)
      (new_faces.length * 3).times do
        material_names << material_for_line(line_number)
      end
      new_faces
    else
      line
    end
  end.flatten
end

#tangentsObject



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/engine/importers/obj_file.rb', line 30

def tangents
  @tangents ||=
    (0...vertex_indices.length).each_slice(3).map do |i1, i2, i3|
      vertex1 = vertices[vertex_indices[i1]]
      normal1 = normals[normal_indices[i1]]
      texture_coord1 = texture_coords[texture_indices[i1]]

      vertex2 = vertices[vertex_indices[i2]]
      normal2 = normals[normal_indices[i2]]
      texture_coord2 = texture_coords[texture_indices[i2]]

      vertex3 = vertices[vertex_indices[i3]]
      normal3 = normals[normal_indices[i3]]
      texture_coord3 = texture_coords[texture_indices[i3]]

      triangle_vertices = [vertex1, vertex2, vertex3]
      triangle_normals = [normal1, normal2, normal3]
      triangle_texture_coords = [texture_coord1, texture_coord2, texture_coord3]

      Engine::TangentCalculator.new(triangle_vertices, triangle_normals, triangle_texture_coords).calculate_tangents
    end.flatten(1)
end

#texture_coordsObject



53
54
55
56
57
58
59
# File 'lib/engine/importers/obj_file.rb', line 53

def texture_coords
  @texture_coords ||=
    @mesh_data.select { |line| line.start_with?("vt ") }.map do |line|
      _, x, y = line.split(" ")
      Vector[x.to_f, y.to_f]
    end
end

#texture_indicesObject



159
160
161
162
163
164
165
166
# File 'lib/engine/importers/obj_file.rb', line 159

def texture_indices
  @texture_indices ||=
    face_lines.map do |line|
      _, v1, v2, v3 = line.split(" ")
      v1, v2, v3 = [v1, v2, v3].map { |v| v.split("/")[1].to_i }
      [v1 - 1, v2 - 1, v3 - 1]
    end.flatten
end

#triangle_area(a, b, c) ⇒ Object



125
126
127
# File 'lib/engine/importers/obj_file.rb', line 125

def triangle_area(a, b, c)
  (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1])).abs / 2.0
end

#vertex_dataObject



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/engine/importers/obj_file.rb', line 215

def vertex_data
  @vertex_data ||=
    (0...vertex_indices.length).map do |i|
      {
        vertex: vertices[vertex_indices[i]],
        normal: normals[normal_indices[i]],
        texture_coord: texture_coords[texture_indices[i]],
        tangent: tangents[i],
        diffuse: materials.dig(material_names[i], :diffuse) || Vector[1, 1, 1],
        specular: materials.dig(material_names[i], :specular) || Vector[1, 1, 1],
        albedo: materials.dig(material_names[i], :albedo) || Vector[1, 1, 1],
      }
    end.map do |data|
      [
        data[:vertex][0], data[:vertex][1], data[:vertex][2],
        data[:texture_coord][0], data[:texture_coord][1],
        data[:normal][0], data[:normal][1], data[:normal][2],
        data[:tangent][0].to_f, data[:tangent][1].to_f, data[:tangent][2].to_f,
        data[:diffuse][0], data[:diffuse][1], data[:diffuse][2],
        data[:specular][0], data[:specular][1], data[:specular][2],
        data[:albedo][0], data[:albedo][1], data[:albedo][2],
      ]
    end.flatten
end

#vertex_indicesObject



150
151
152
153
154
155
156
157
# File 'lib/engine/importers/obj_file.rb', line 150

def vertex_indices
  @vertex_indices ||=
    face_lines.map do |line|
      _, v1, v2, v3 = line.split(" ")
      v1, v2, v3 = [v1, v2, v3].map { |v| v.split("/").first.to_i }
      [v1 - 1, v2 - 1, v3 - 1]
    end.flatten
end

#verticesObject



14
15
16
17
18
19
20
# File 'lib/engine/importers/obj_file.rb', line 14

def vertices
  @vertices ||=
    @mesh_data.select { |line| line.start_with?("v ") }.map do |line|
      _, x, y, z = line.split(" ")
      Vector[x.to_f, y.to_f, z.to_f]
    end
end