class Mittsu::Geometry

Constants

MorphNormal
Normal

Attributes

bounding_box[RW]
bounding_sphere[RW]
colors[RW]
dynamic[RW]
face_vertex_uvs[RW]
faces[RW]
has_tangents[RW]
id[R]
line_distances[RW]
morph_colors[RW]
morph_normals[RW]
morph_targets[RW]
name[RW]
skin_indices[RW]
skin_weights[RW]
type[R]
uuid[R]
vertices[RW]

Public Class Methods

new() click to toggle source
Calls superclass method
# 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

Public Instance Methods

apply_matrix(matrix) click to toggle source
# 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
center() click to toggle source
# 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
clone() click to toggle source
# 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_box() click to toggle source
# 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_sphere() click to toggle source
# 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_normals() click to toggle source
# 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_distances() click to toggle source
# 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_normals() click to toggle source
# 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_tangents() click to toggle source
# 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) click to toggle source
# 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
dispose() click to toggle source
# File lib/mittsu/core/geometry.rb, line 561
def dispose
  self.dispatch_event type: :dispose
end
from_buffer_geometry(geometry) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_vertices() click to toggle source
# 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_json() click to toggle source
# 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

Private Instance Methods

get_color_index(color, color_hash, colors) click to toggle source
# File lib/mittsu/core/geometry.rb, line 579
def get_color_index(color, color_hash, colors)
  hash = color.r.to_s + color.g.to_s + color.b.to_s
  return colors_hash[hash] unless colors_hash[hash].nil?
  colors_hash[hash] = colors.length
  colors << color.get_hex
  colors_hash[hash]
end
get_normal_index(normal, normals_hash, normals) click to toggle source
# File lib/mittsu/core/geometry.rb, line 571
def get_normal_index(normal, normals_hash, normals)
  hash = normal.x.to_s + normal.y.to_s + normal.z.to_s
  return normals_hash[hash] unless normals_hash[hash].nil?
  normals_hash[hash] = normals.length / 3
  normals << normal.x << normal.y << normal.z
  normals_hash[hash]
end
get_uv_index(uv, uvs_hash, uvs) click to toggle source
# File lib/mittsu/core/geometry.rb, line 587
def get_uv_index(uv, uvs_hash, uvs)
  hash = uv.x.to_s + uv.y.to_s
  return uvs_hash[hash] unless uvs_hash[hash].nil?
  uvs_hash[hash] = uvs.length / 2
  uvs << uv.x << uv.y
  return uvs_hash[hash]
end
set_bit(value, position, enabled) click to toggle source
# File lib/mittsu/core/geometry.rb, line 567
def set_bit(value, position, enabled)
  enabled ? value | (1 << position) : value & (~(1 << position))
end