Class: Mittsu::Geometry

Inherits:
Object
  • Object
show all
Includes:
EventDispatcher
Defined in:
lib/mittsu/core/geometry.rb

Defined Under Namespace

Classes: MorphNormal, Normal

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from EventDispatcher

#add_event_listener, #dispatch_event, #has_event_listener, #remove_event_listener

Constructor Details

#initializeGeometry

Returns a new instance of Geometry.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/mittsu/core/geometry.rb', line 16

def initialize
  super

  @id = (@@id ||= 1).tap { @@id += 1 }

  @uuid = SecureRandom.uuid

  @name = ''
  @type = 'Geometry'

  @vertices = []
  @colors = [] # one-to-one vertex colors, used in Points and Line

  @faces = []

  @face_vertex_uvs = [[]]

  @morph_targets = []
  @morph_colors = []
  @morph_normals = []

  @skin_weights = []
  @skin_indices = []

  @line_distances = []

  @has_tangents = false

  @vertices_need_update = false
  @morph_targets_need_update = false
  @elements_need_update = false
  @uvs_need_update = false
  @normals_need_update = false
  @tangents_need_update = false
  @colors_need_update = false
  @line_distances_need_update = false
  @groups_need_update = false

  @_listeners = {}
end

Instance Attribute Details

#bounding_boxObject

Returns the value of attribute bounding_box.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def bounding_box
  @bounding_box
end

#bounding_sphereObject

Returns the value of attribute bounding_sphere.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def bounding_sphere
  @bounding_sphere
end

#colorsObject

Returns the value of attribute colors.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def colors
  @colors
end

#dynamicObject

Returns the value of attribute dynamic.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def dynamic
  @dynamic
end

#face_vertex_uvsObject

Returns the value of attribute face_vertex_uvs.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def face_vertex_uvs
  @face_vertex_uvs
end

#facesObject

Returns the value of attribute faces.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def faces
  @faces
end

#has_tangentsObject

Returns the value of attribute has_tangents.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def has_tangents
  @has_tangents
end

#idObject (readonly)

Returns the value of attribute id.



14
15
16
# File 'lib/mittsu/core/geometry.rb', line 14

def id
  @id
end

#line_distancesObject

Returns the value of attribute line_distances.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def line_distances
  @line_distances
end

#morph_colorsObject

Returns the value of attribute morph_colors.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def morph_colors
  @morph_colors
end

#morph_normalsObject

Returns the value of attribute morph_normals.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def morph_normals
  @morph_normals
end

#morph_targetsObject

Returns the value of attribute morph_targets.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def morph_targets
  @morph_targets
end

#nameObject

Returns the value of attribute name.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def name
  @name
end

#skin_indicesObject

Returns the value of attribute skin_indices.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def skin_indices
  @skin_indices
end

#skin_weightsObject

Returns the value of attribute skin_weights.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def skin_weights
  @skin_weights
end

#typeObject (readonly)

Returns the value of attribute type.



14
15
16
# File 'lib/mittsu/core/geometry.rb', line 14

def type
  @type
end

#uuidObject (readonly)

Returns the value of attribute uuid.



14
15
16
# File 'lib/mittsu/core/geometry.rb', line 14

def uuid
  @uuid
end

#verticesObject

Returns the value of attribute vertices.



10
11
12
# File 'lib/mittsu/core/geometry.rb', line 10

def vertices
  @vertices
end

Instance Method Details

#apply_matrix(matrix) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/mittsu/core/geometry.rb', line 57

def apply_matrix(matrix)
  normal_matrix = Mittsu::Matrix3.new.get_normal_matrix(matrix)
  @vertices.each do |vertex|
    vertex.apply_matrix4(matrix)
  end
  @faces.each do |face|
    face.normal.apply_matrix3(normal_matrix).normalize
    face.vertex_normals.each do |vertex_normal|
      vertex_normal.apply_matrix3(normal_matrix).normalize
    end
  end
  if @bounding_box
    self.compute_bounding_box
  end
  if @bounding_sphere
    self.compute_bounding_sphere
  end
  @vertices_need_update = true
  @normals_need_update = true
end

#centerObject



145
146
147
148
149
150
# File 'lib/mittsu/core/geometry.rb', line 145

def center
  self.compute_bounding_box
  offset = @bounding_box.center.negate
  self.apply_matrix(Mittsu::Matrix4.new.set_position(offset))
  offset
end

#cloneObject



540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/mittsu/core/geometry.rb', line 540

def clone
  geometry = Mittsu::Geometry.new
  @vertices.each do |v|
    geometry.vertices << v.clone
  end
  @faces.each do |f|
    geometry.faces << f.clone
  end
  @face_vertex_uvs.each_with_index do |face_vertex_uvs, i|
    geometry.face_vertex_uvs[i] ||= []
    face_vertex_uvs.each do |uvs|
      uvs_copy = []
      uvs.each do |uv|
        uvs_copy << uv.clone
      end
      geometry.face_vertex_uvs[i] << uvs_copy
    end
  end
  geometry
end

#compute_bounding_boxObject



337
338
339
340
# File 'lib/mittsu/core/geometry.rb', line 337

def compute_bounding_box
  @bounding_box ||= Mittsu::Box3.new
  @bounding_box.set_from_points(@vertices)
end

#compute_bounding_sphereObject



342
343
344
345
# File 'lib/mittsu/core/geometry.rb', line 342

def compute_bounding_sphere
  @bounding_sphere ||= Mittsu::Sphere.new
  @bounding_sphere.set_from_points(@vertices)
end

#compute_face_normalsObject



152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/mittsu/core/geometry.rb', line 152

def compute_face_normals
  cb, ab = Mittsu::Vector3.new, Mittsu::Vector3.new
  @faces.each do |face|
    v_a = @vertices[face.a]
    v_b = @vertices[face.b]
    v_c = @vertices[face.c]
    cb.sub_vectors(v_c, v_b)
    ab.sub_vectors(v_a, v_b)
    cb.cross(ab)
    cb.normalize
    face.normal.copy(cb)
  end
end

#compute_line_distancesObject



327
328
329
330
331
332
333
334
335
# File 'lib/mittsu/core/geometry.rb', line 327

def compute_line_distances
  d = 0
  @vertices.each_with_index do |vertex, i|
    if i > 0
      d += vertex.distance_to(@vertices[i - 1])
    end
    @line_distances[i] = d
  end
end

#compute_morph_normalsObject



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/mittsu/core/geometry.rb', line 201

def compute_morph_normals
  # save original normals
  # - create temp variables on first access
  #   otherwise just copy (for faster repeated calls)
  @_original_face_normal ||= []
  @_original_vertex_normals ||= []
  @faces.each_with_index do |face, f|
    if !@_original_face_normal[f]
      @_original_face_normal[f] = face.normal.clone
    else
      @_original_face_normal[f].copy(face.normal)
    end
    @_original_vertex_normals[f] ||= []
    face.vertex_normals.each_with_index do |vnorm, i|
      if !@_original_vertex_normals[f][i]
        @_original_vertex_normals[f][i] = vnorm.clone
      else
        @_original_vertex_normals[f][i].copy(face.vertex_normals[i])
      end
    end
  end

  # use temp geometry to compute face and vertex normals for each morph
  tmp_geo = Mittsu::Geometry.new
  tmp_geo.faces = @faces
  @morph_targets.each_with_index do |morph_target, i|
    # create on first access
    if !@morph_normals[i]
      @morph_normals[i] = MorphNormal.new([], [])
      dst_normals_face = @morph_normals[i].face_normals
      dst_normals_vertex = @morph_normals[i].vertex_normals
      @faces.each do
        face_normal = Mittsu::Vector3.new
        vertex_normals = Normal.new(Mittsu::Vector3.new, Mittsu::Vector3.new, Mittsu::Vector3.new)
        dst_normals_face << face_normal
        dst_normals_vertex << vertex_normals
      end
    end
    # set vertices to morph target
    tmp_geo.vertices = @morph_targets[i].vertices
    # compute morph normals
    tmp_geo.compute_face_normals
    tmp_geo.compute_vertex_normals
    # store morph normals
    @faces.each_with_index do |face, f|
      face_normal = @morph_normals[i].face_normals[f]
      vertex_normals = @morph_normals[i].vertex_normals[f]
      face_normal.copy(face.normal)
      vertex_normals.a.copy(face.vertex_normals[0])
      vertex_normals.b.copy(face.vertex_normals[1])
      vertex_normals.c.copy(face.vertex_normals[2])
    end
  end
  # restore original normals
  @faces.each_with_index do |face, f|
    face.normal = @_original_face_normal[f]
    face.vertex_normals = @_original_vertex_normals[f]
  end
end

#compute_tangentsObject



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/mittsu/core/geometry.rb', line 261

def compute_tangents
  # based on http:#www.terathon.com/code/tangent.html
  # tangents go to vertices
  tan1 = []; tan2 = [],
  sdir = Mittsu::Vector3.new; tdir = Mittsu::Vector3.new
  tmp = Mittsu::Vector3.new; tmp2 = Mittsu::Vector3.new
  n = Mittsu::Vector3.new
  uv = []
  @vertices.each_index do |v|
    tan1[v] = Mittsu::Vector3.new
    tan2[v] = Mittsu::Vector3.new
  end
  handle_triangle = -> (context, a, b, c, ua, ub, uc) {
    v_a = context.vertices[a]
    v_b = context.vertices[b]
    v_c = context.vertices[c]
    uv_a = uv[ua]
    uv_b = uv[ub]
    uv_c = uv[uc]
    x1 = v_b.x - v_a.x
    x2 = v_c.x - v_a.x
    y1 = v_b.y - v_a.y
    y2 = v_c.y - v_a.y
    z1 = v_b.z - v_a.z
    z2 = v_c.z - v_a.z
    s1 = uv_b.x - uv_a.x
    s2 = uv_c.x - uv_a.x
    t1 = uv_b.y - uv_a.y
    t2 = uv_c.y - uv_a.y
    r = 1.0 / (s1 * t2 - s2 * t1)
    sdir.set((t2 * x1 - t1 * x2) * r,
          (t2 * y1 - t1 * y2) * r,
          (t2 * z1 - t1 * z2) * r)
    tdir.set((s1 * x2 - s2 * x1) * r,
          (s1 * y2 - s2 * y1) * r,
          (s1 * z2 - s2 * z1) * r)
    tan1[a].add(sdir)
    tan1[b].add(sdir)
    tan1[c].add(sdir)
    tan2[a].add(tdir)
    tan2[b].add(tdir)
    tan2[c].add(tdir)
  }
  @faces.each_with_index do |face, f|
    uv = @face_vertex_uvs[0][f] # use UV layer 0 for tangents
    handle_triangle[self, face.a, face.b, face.c, 0, 1, 2]
  end
  face_index = ['a', 'b', 'c', 'd']
  @faces.each_with_index do |face, f|
    [face.vertex_normals.length, 3].min.times do |i|
      n.copy(face.vertex_normals[i])
      vertex_index = face[face_index[i]]
      t = tan1[vertex_index]
      # Gram-Schmidt orthogonalize
      tmp.copy(t)
      tmp.sub(n.multiply_scalar(n.dot(t))).normalize
      # Calculate handedness
      tmp2.cross_vectors(face.vertex_normals[i], t)
      test = tmp2.dot(tan2[vertex_index])
      w = (test < 0.0) ? - 1.0 : 1.0
      face.vertex_tangents[i] = Mittsu::Vector4.new(tmp.x, tmp.y, tmp.z, w)
    end
  end
  @has_tangents = true
end

#compute_vertex_normals(area_weighted = false) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/mittsu/core/geometry.rb', line 166

def compute_vertex_normals(area_weighted = false)
  vertices = Array.new(@vertices.length)
  @vertices.length.times do |v|
    vertices[v] = Mittsu::Vector3.new
  end
  if area_weighted
    # vertex normals weighted by triangle areas
    # http:#www.iquilezles.org/www/articles/normals/normals.htm
    cb = Mittsu::Vector3.new, ab = Mittsu::Vector3.new
    @faces.each do |face|
      v_a = @vertices[face.a]
      v_b = @vertices[face.b]
      v_c = @vertices[face.c]
      cb.sub_vectors(v_c, v_b)
      ab.sub_vectors(v_a, v_b)
      cb.cross(ab)
      vertices[face.a].add(cb)
      vertices[face.b].add(cb)
      vertices[face.c].add(cb)
    end
  else
    @faces.each do |face|
      vertices[face.a].add(face.normal)
      vertices[face.b].add(face.normal)
      vertices[face.c].add(face.normal)
    end
  end
  @vertices.each(&:normalize)
  @faces.each do |face|
    face.vertex_normals[0] = vertices[face.a].clone
    face.vertex_normals[1] = vertices[face.b].clone
    face.vertex_normals[2] = vertices[face.c].clone
  end
end

#disposeObject



561
562
563
# File 'lib/mittsu/core/geometry.rb', line 561

def dispose
  self.dispatch_event type: :dispose
end

#from_buffer_geometry(geometry) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/mittsu/core/geometry.rb', line 78

def from_buffer_geometry(geometry)
  scope = self
  vertices = geometry[:position].array
  indices = geometry[:index].nil? ? nil : geometry[:index].array
  normals = geometry[:normal].nil? ? nil : geometry[:normal].array
  colors = geometry[:color].nil? ? nil : geometry[:color].array
  uvs = geometry[:uv].nil? ? nil : geometry[:uv].array
  temp_normals = []
  temp_uvs = []
  i, j = 0, 0
  while i < vertices.length
    scope.vertices << Mittsu::Vector3.new(vertices[i], vertices[i + 1], vertices[i + 2])
    if normals
      temp_normals << Mittsu::Vector3.new(normals[i], normals[i + 1], normals[i + 2])
    end
    if colors
      scope.colors << Mittsu::Color.new(colors[i], colors[i + 1], colors[i + 2])
    end
    if uvs
      temp_uvs << Mittsu::Vector2.new(uvs[j], uvs[j + 1])
    end
    i += 3; j += 2
  end
  add_face = -> (a, b, c) {
    vertex_normals = normals.nil? ? [] : [temp_normals[a].clone, temp_normals[b].clone, temp_normals[c].clone]
    vertex_colors = colors.nil? ? [] : [scope.colors[a].clone, scope.colors[b].clone, scope.colors[c].clone]
    scope.faces << Mittsu::Face3.new(a, b, c, vertex_normals, vertex_colors)
    if uvs
      scope.face_vertex_uvs[0] << [temp_uvs[a].clone, temp_uvs[b].clone, temp_uvs[c].clone]
    end
  }
  if indices
    draw_calls = geometry.draw_calls
    if !draw_calls.empty?
      draw_calls.each do |draw_call|
        start = draw_call.start
        count = draw_call.count
        index = draw_call.index
        j = start
        jl = start + count
        while j < jl
          add_face[index + indices[j], index + indices[j + 1], index + indices[j + 2]]
          j += 3
        end
      end
    else
      indices.each_slice(3).with_index do |index|
        add_face[*index]
      end
    end
  else
    i = 0, il = vertices.length / 3
    while i < il
      add_face[i, i + 1, i + 2]
      i += 3
    end
  end
  self.compute_face_normals
  if geometry.bounding_box
    @bounding_box = geometry.bounding_box.clone
  end
  if geometry.bounding_sphere
    @bounding_sphere = geometry.bounding_sphere.clone
  end
  self
end

#merge(geometry, matrix = nil, material_index_offset = nil) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/mittsu/core/geometry.rb', line 347

def merge(geometry, matrix = nil, material_index_offset = nil)
  if !geometry.is_a? Mittsu::Geometry
    puts('ERROR: Mittsu::Geometry#merge: geometry not an instance of Mittsu::Geometry.', geometry.inspect)
    return
  end
  vertexOffset = @vertices.length
  vertices1 = @vertices
  vertices2 = geometry.vertices
  faces1 = @faces
  faces2 = geometry.faces
  uvs1 = @face_vertex_uvs[0]
  uvs2 = geometry.face_vertex_uvs[0]
  material_index_offset ||= 0
  normal_matrix = matrix.nil? ? nil : Mittsu::Matrix3.new.get_normal_matrix(matrix)
  # vertices
  vertices2.each do |vertex|
    vertex_copy = vertex.clone
    vertex_copy.apply_matrix4(matrix) unless matrix.nil?
    vertices1 << vertex_copy
  end
  # faces
  faces2.each do |face|
    face_vertex_normals = face.vertex_normals
    face_vertex_colors = face.vertex_colors
    face_copy = Mittsu::Face3.new(face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset)
    face_copy.normal.copy(face.normal)
    if normal_matrix != nil
      face_copy.normal.apply_matrix3(normal_matrix).normalize unless normal_matrix.nil?
    end
    face_vertex_normals.each do |normal|
      normal = normal.clone
      normal.apply_matrix3(normal_matrix).normalize unless normal_matrix.nil?
      face_copy.vertex_normals << normal
    end
    face_copy.color.copy(face.color)
    face_vertex_colors.each do |color|
      face_copy.vertex_colors << color.clone
    end
    face_copy.materialIndex = face.materialIndex + material_index_offset
    faces1 << face_copy
  end
  # uvs
  uvs2.each do |uv|
    continue if uv.nil?
    uv_copy = []
    uv.each do |u|
      uv_copy << u.clone
    end
    uvs1 << uv_copy
  end
end

#merge_mesh(mesh) ⇒ Object



399
400
401
402
403
404
405
406
# File 'lib/mittsu/core/geometry.rb', line 399

def merge_mesh(mesh)
  if mesh.is_a?(Mittsu::Mesh) == false
    puts('ERROR: Mittsu::Geometry#merge_mesh: mesh not an instance of Mittsu::Mesh.', mesh.inspect)
    return
  end
  mesh.matrix_auto_update && mesh.update_matrix
  self.merge(mesh.geometry, mesh.matrix)
end

#merge_verticesObject



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/mittsu/core/geometry.rb', line 408

def merge_vertices
  vertices_map = {} # Hashmap for looking up vertice by position coordinates (and making sure they are unique)
  unique = []; changes = []
  precision_points = 4 # number of decimal points, eg. 4 for epsilon of 0.0001
  precision = 10 ** precision_points
  @vertices.each_with_index do |v, i|
    key = "#{(v.x * precision).round}_#{(v.y * precision).round}_#{(v.z * precision).round}"
    if vertices_map[key].nil?
      vertices_map[key] = i
      unique << v
      changes[i] = unique.length - 1
    else
      #console.log('Duplicate vertex found. ', i, ' could be using ', vertices_map[key])
      changes[i] = changes[vertices_map[key]]
    end
  end
  # if faces are completely degenerate after merging vertices, we
  # have to remove them from the geometry.
  face_indices_to_remove = []
  @faces.each_with_index do |face, i|
    face.a = changes[face.a]
    face.b = changes[face.b]
    face.c = changes[face.c]
    indices = [face.a, face.b, face.c]
    # if any duplicate vertices are found in a Face3
    # we have to remove the face as nothing can be saved
    3.times do |n|
      if indices[n] == indices[(n + 1) % 3]
        face_indices_to_remove << i
        break
      end
    end
  end
  face_indices_to_remove.reverse_each do |idx|
    @faces.delete_at idx
    @face_vertex_uvs.each do |uv|
      uv.delete_at idx
    end
  end
  # Use unique set of vertices
  diff = @vertices.length - unique.length
  @vertices = unique
  diff
end

#to_jsonObject



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/mittsu/core/geometry.rb', line 453

def to_json
  output = {
    metadata: {
      version: 4.0,
      type: 'BufferGeometry',
      generator: 'BufferGeometryExporter'
    },
    uuid: self.uuid,
    type: @type
  }
  output.name = @name unless @name.nil? || @name.empty?
  if @parameters
    @parameters.each do |(key, parameter)|
      output[key] = parameter unless parameter.nil?
    end
    return output
  end
  vertices = []
  @vertices.each do |vert|
    vertices << vert.x << vert.y << vert.z
  end
  faces = []
  normals = []
  normals_hash = {}
  colors = []
  colors_hash = {}
  uvs = []
  uvs_hash = {}
  @faces.each_with_index do |face, i|
    has_material = false # face.materialIndex != nil
    has_face_uv = false # deprecated
    has_face_vertex_uv = @face_vertex_uvs[0][i] != nil
    has_face_normal = face.normal.length > 0
    has_face_vertex_normal = face.vertex_normals.length > 0
    has_face_color = face.color.r != 1 || face.color.g != 1 || face.color.b != 1
    has_face_vertex_color = face.vertex_colors.length > 0
    face_type = 0
    face_type = set_bit(face_type, 0, 0)
    face_type = set_bit(face_type, 1, has_material)
    face_type = set_bit(face_type, 2, has_face_uv)
    face_type = set_bit(face_type, 3, has_face_vertex_uv)
    face_type = set_bit(face_type, 4, has_face_normal)
    face_type = set_bit(face_type, 5, has_face_vertex_normal)
    face_type = set_bit(face_type, 6, has_face_color)
    face_type = set_bit(face_type, 7, has_face_vertex_color)
    faces << face_type
    faces << face.a << face.b << face.c
    # if has_material
    #   faces << face.materialIndex
    # end
    if has_face_vertex_uv
      face_vertex_uvs = @face_vertex_uvs[0][i]
      faces << get_uv_index(face_vertex_uvs[0], uvs_hash, uvs)
      faces << get_uv_index(face_vertex_uvs[1], uvs_hash, uvs)
      faces << get_uv_index(face_vertex_uvs[2], uvs_hash, uvs)
    end
    if has_face_normal
      faces << get_normal_index(face.normal, normals_hash, normals)
    end
    if has_face_vertex_normal
      vertex_normals = face.vertex_normals
      faces << get_normal_index(vertex_normals[0], normals_hash, normals)
      faces << get_normal_index(vertex_normals[1], normals_hash, normals)
      faces << get_normal_index(vertex_normals[2], normals_hash, normals)
    end
    if has_face_color
      faces << get_color_index(face.color)
    end
    if has_face_vertex_color
      vertex_colors = face.vertex_colors
      faces << get_color_index(vertex_colors[0], colors_hash, colors)
      faces << get_color_index(vertex_colors[1], colors_hash, colors)
      faces << get_color_index(vertex_colors[2], colors_hash, colors)
    end
  end
  output[:data] = {}
  output[:data][:vertices] = vertices
  output[:data][:normals] = normals
  output[:data][:colors] = colors unless colors.empty?
  output[:data][:uvs] = [uvs] unless uvs.empty? # temporal backward compatibility
  output[:data][:faces] = faces

  #

  output
end