Load 3d mesh form .obj' file.
  
  Args:
    fn: Input file name or file-like object.
    
  Returns:
    dictionary with the following keys (some of which may be missing):
      position: np.float32, (n, 3) array, vertex positions
      uv: np.float32, (n, 2) array, vertex uv coordinates
      normal: np.float32, (n, 3) array, vertex uv normals
      face: np.int32, (k*3,) traingular face indices

def load_obj(fn):
  """Load 3d mesh form .obj' file.
  
  Args:
    fn: Input file name or file-like object.
    
  Returns:
    dictionary with the following keys (some of which may be missing):
      position: np.float32, (n, 3) array, vertex positions
      uv: np.float32, (n, 2) array, vertex uv coordinates
      normal: np.float32, (n, 3) array, vertex uv normals
      face: np.int32, (k*3,) traingular face indices
  """
  position = [np.zeros(3, dtype=np.float32)]
  normal = [np.zeros(3, dtype=np.float32)]
  uv = [np.zeros(2, dtype=np.float32)]
  
  tuple2idx = OrderedDict()
  trinagle_indices = []
  
  input_file = open(fn) if isinstance(fn, str) else fn
  for line in input_file:
    line = line.strip()
    if not line or line[0] == '#':
      continue
    line = line.split(' ', 1)
    tag = line[0]
    if len(line) > 1:
      line = line[1]
    else:
      line = ''
    if tag == 'v':
      position.append(np.fromstring(line, sep=' '))
    elif tag == 'vt':
      uv.append(np.fromstring(line, sep=' '))
    elif tag == 'vn':
      normal.append(np.fromstring(line, sep=' '))
    elif tag == 'f':
      output_face_indices = []
      for chunk in line.split():
        # tuple order: pos_idx, uv_idx, normal_idx
        vt = _parse_vertex_tuple(chunk)
        if vt not in tuple2idx:  # create a new output vertex?
          tuple2idx[vt] = len(tuple2idx)
        output_face_indices.append(tuple2idx[vt])
      # generate face triangles
      for i in range(1, len(output_face_indices)-1):
        for vi in [0, i, i+1]:
          trinagle_indices.append(output_face_indices[vi])
  
  outputs = {}
  outputs['face'] = np.int32(trinagle_indices)
  pos_idx, uv_idx, normal_idx = np.int32(list(tuple2idx)).T
  if np.any(pos_idx):
    outputs['position'] = _unify_rows(position)[pos_idx]
  if np.any(uv_idx):
    outputs['uv'] = _unify_rows(uv)[uv_idx]
  if np.any(normal_idx):
    outputs['normal'] = _unify_rows(normal)[normal_idx]
  return outputs