예제 #1
0
    def make_node(self, parent_node, obj=None):
        node = None

        if obj is None:
            node = parent_node

        else:
            if obj.type == 'EMPTY':
                node = self.make_empty(parent_node, obj)

            elif obj.type == 'ARMATURE':
                node = self.make_armature(parent_node, obj)

            elif obj.type == 'MESH':
                node = self.make_mesh(parent_node, obj)

            elif obj.type in ('LIGHT', 'LAMP'):
                if obj.data.type in ('SPOT', 'POINT'):
                    node = self.make_light(parent_node, obj)

        if node is None:
            return

        # make children of the current node
        if obj is None:  # root objects
            children = filter(lambda o: not o.parent, bpy.data.objects)
        else:  # children on current object
            children = obj.children

        for child in children:
            # print(child)
            if not is_object_visible(child) and not is_collision(child):
                continue

            if self._export_type == 'collision':
                if child.type in ('ARMATURE', 'LIGHT', 'LAMP'):
                    continue

                if child.type == 'MESH' and not is_collision(child):
                    continue

            self.make_node(node, child)
예제 #2
0
    def can_merge(self, obj):
        if not self._merge:
            return False

        collection = get_object_collection(obj)
        if not collection:
            return False

        if is_collision(obj):
            return False

        obj_props = get_object_properties(obj)
        if obj_props.get('type') in NOT_MERGED_TYPES:
            return False

        return True
예제 #3
0
    def _setup_node(self, node, obj=None, can_merge=False):
        if obj is None:
            return

        armature = get_armature(obj)
        obj_matrix = self._transform(get_object_matrix(obj, armature=armature))

        # get custom object properties
        obj_props = get_object_properties(obj)

        if not can_merge and not armature:
            if self._geom_scale == 1:
                node.update({
                    'rotation':
                    quat_to_list(obj_matrix.to_quaternion()),
                    'scale':
                    list(obj_matrix.to_scale()),
                    'translation':
                    list(obj_matrix.to_translation()),
                    # 'matrix': matrix_to_list(obj_matrix),
                })
            else:
                x, y, z = list(obj_matrix.to_translation())
                x *= self._geom_scale
                y *= self._geom_scale
                z *= self._geom_scale
                node.update({
                    'rotation':
                    quat_to_list(obj_matrix.to_quaternion()),
                    'scale':
                    list(obj_matrix.to_scale()),
                    'translation': [x, y, z],
                })

        # setup collisions
        if not can_merge and is_collision(
                obj) and obj_props.get('type') != 'Portal':
            collision = {}
            node['extensions'] = {
                'BLENDER_physics': collision,
            }

            # collision shape
            shape = {
                'shapeType':
                obj.rigid_body.collision_shape,
                'boundingBox': [
                    obj.dimensions[i] / obj_matrix.to_scale()[i] *
                    self._geom_scale for i in range(3)
                ],
            }
            if obj.rigid_body.collision_shape == 'MESH':
                shape['mesh'] = node.pop('mesh')

            collision['collisionShapes'] = [shape]
            collision['static'] = obj.rigid_body.type == 'PASSIVE'

            # don't actually collide (ghost)
            if (not obj.collision or not obj.collision.use):
                collision['intangible'] = True

            if self._set_origin:
                obj.select_set(state=True)
                set_active_object(obj)
                x1, y1, z1 = obj.location
                # set origin to the center of bounds
                bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY',
                                          center='BOUNDS')
                x2, y2, z2 = obj.location
                if 'extras' not in node:
                    node['extras'] = {}
                node['extras']['origin'] = [x2 - x1, y2 - y1, z2 - z1]

        # setup custom properties with tags
        if (obj_props or can_merge) and 'extras' not in node:
            node['extras'] = {}

        for k, v in obj_props.items():
            if node['extras'].get(k):  # tag exists
                tag = node['extras'].get(k)

            if type(v) in (tuple, list, dict):
                tag = json.dumps(v)
            else:
                tag = '{}'.format(v)

            node['extras'][k] = tag

        # if can_merge and 'type' not in obj_props:
        if can_merge:
            node['extras']['type'] = 'Merged'
예제 #4
0
    def _setup_node(self, node, obj=None, can_merge=False):
        if obj is None:
            return

        armature = get_armature(obj)
        obj_matrix = get_object_matrix(obj, armature=armature)

        # get custom object properties
        obj_props = get_object_properties(obj)

        if not can_merge and not armature:
            node.add_matrix4(matrix_to_panda(obj_matrix))

        if obj_props.get('type') == 'Portal':
            node.set_portal_flag(True)

        # setup collisions
        if not can_merge and is_collision(obj):
            # collision name
            node.set_collision_name(obj.name)

            # collision solid type
            shape = {
                'BOX': EggGroup.CST_box,
                'SPHERE': EggGroup.CST_sphere,
                'CAPSULE': EggGroup.CST_tube,
                'MESH': EggGroup.CST_polyset,
            }.get(obj.rigid_body.collision_shape, EggGroup.CST_polyset)

            # custom shape
            if obj.rigid_body.collision_shape == 'CONVEX_HULL':
                # trying to guess the best shape
                polygons = list(
                    filter(lambda x: isinstance(x, EggPolygon),
                           node.get_children()))
                if len(polygons) == 1 and polygons[0].is_planar():
                    # shape = EggGroup.CST_plane  # <- is it infinite?
                    shape = EggGroup.CST_polygon
            node.set_cs_type(shape)

            # collision flags
            # inherit collision by children nodes?
            flags = EggGroup.CF_descend
            if (not obj.collision or not obj.collision.use):
                # don't actually collide (ghost)
                flags |= EggGroup.CF_intangible
            node.set_collide_flags(flags)

        # setup custom properties with tags
        for k, v in obj_props.items():
            if node.get_tag(k):  # tag exists
                tag = node.get_tag(k)

            if type(v) in (tuple, list, dict):
                tag = json.dumps(v)
            else:
                tag = '{}'.format(v)
            node.set_tag(k, tag)

        if can_merge and 'type' not in obj_props:
            node.set_tag('type', 'Merged')
예제 #5
0
    def make_geom(self, node, obj, can_merge=False):
        triangulate = not is_collision(obj)
        if self._geom_scale != 1:
            obj.scale.x = self._geom_scale
            obj.scale.y = self._geom_scale
            obj.scale.z = self._geom_scale
        apply_modifiers(obj, triangulate=triangulate)
        mesh = obj2mesh(obj, triangulate=triangulate)

        # get or create materials and textures
        egg_materials = {}
        egg_textures = {}
        egg_material_textures = {}
        if not self._no_materials and not is_collision(obj):
            for material in mesh.materials.values():
                # material
                for child in self._root.get_children():  # existing material
                    if (isinstance(child, EggMaterial) and
                            child.get_name() == material.name):
                        egg_materials[material.name] = child
                        break
                else:  # new material
                    egg_material = self.make_material(material)
                    self._root.add_child(egg_material)
                    egg_materials[material.name] = egg_material

                # material -> textures
                if material.name not in egg_material_textures:
                    egg_material_textures[material.name] = {}

                # textures
                if not self._no_textures:
                    for type_, _, egg_texture in self.make_textures(material):
                        tname = egg_texture.get_name()
                        for child in self._root.get_children():  # existing texture
                            if (isinstance(child, EggTexture) and
                                    child.get_name() == tname):
                                egg_textures[tname] = child
                                egg_material_textures[material.name][tname] = child
                                break
                        else:  # new texture
                            self._root.add_child(egg_texture)
                            egg_textures[tname] = egg_texture
                            egg_material_textures[material.name][tname] = egg_texture

        # get or create vertex pool
        egg_vertex_pool = None
        egg_vertex_id = 0
        if can_merge:
            for child in node.get_children():  # existing vertex pool
                if isinstance(child, EggVertexPool):
                    egg_vertex_pool = child
                    egg_vertex_id = egg_vertex_pool.get_highest_index()
                    break

        if egg_vertex_pool is None:  # new vertex pool
            egg_vertex_pool = EggVertexPool(node.get_name())
            egg_vertex_id = egg_vertex_pool.get_highest_index()
            node.add_child(egg_vertex_pool)

        # get armature and joints
        armature = get_armature(obj)
        egg_joints = {}
        if armature:
            for child in self._root.get_children():
                if (isinstance(child, EggGroup) and
                        child.get_dart_type() == EggGroup.DT_structured and
                        child.get_name() == armature.name):
                    egg_joints = self._get_joints(child)

        sharp_vertices = {}
        uv_tb = {}
        if not is_collision(obj):
            sharp_vertices = self.get_sharp_vertices(mesh)
            uv_tb = self.get_tangent_bitangent(mesh)
        egg_vertices = {}

        obj_matrix = get_object_matrix(obj, armature)
        parent_obj_matrix = obj_matrix
        if armature:
            parent_obj_matrix = get_object_matrix(armature)

        for polygon in mesh.polygons:
            # <-- polygon
            material = None
            mname = None
            if not self._no_materials:
                try:
                    material = mesh.materials[polygon.material_index]
                    mname = material.name
                except IndexError:
                    pass

            # make polygon
            egg_polygon = EggPolygon(mname or node.get_name())

            # set material and textures
            if material and not self._no_materials and not is_collision(obj):
                if mname in egg_materials:
                    egg_polygon.set_material(egg_materials[mname])

                # set textures
                if mname in egg_material_textures and not self._no_textures:
                    for egg_texture in egg_material_textures[mname].values():
                        egg_polygon.add_texture(egg_texture)

            # vertices
            for i, vertex_id in enumerate(polygon.vertices):
                # i is vertex counter inside a polygon
                # (0, 1, 2) for triangle
                # vertex_id is reusable id,
                # because multiple polygons can share the same vertices

                # <-- vertex
                vertex = mesh.vertices[vertex_id]
                use_smooth = (
                    polygon.use_smooth and
                    vertex_id not in sharp_vertices and
                    not is_collision(obj))

                # try to reuse shared vertices
                if (polygon.use_smooth and
                        vertex_id in egg_vertices and
                        not is_collision(obj)):
                    shared = False
                    for egg_vertex in egg_vertices[vertex_id]:
                        loop_id = polygon.loop_indices[i]
                        egg_vertex_uv = egg_vertex.get_uv_obj(
                            self._get_uv_name(mesh.uv_layers.active))

                        if not egg_vertex_uv:
                            egg_polygon.add_vertex(egg_vertex)
                            shared = True
                            break

                        if self.can_share_vertex(mesh, loop_id, egg_vertex_uv.get_uv()):
                            egg_polygon.add_vertex(egg_vertex)
                            shared = True
                            break
                    if shared:
                        continue

                # make new vertex data
                egg_vertex = self.make_vertex(
                    parent_obj_matrix, obj_matrix, polygon, vertex,
                    use_smooth=use_smooth)

                # uv layers
                if not is_collision(obj):
                    for uv_name, uv_layer in mesh.uv_layers.items():
                        # <-- vertex uv
                        loop_id = polygon.loop_indices[i]
                        uv_loop = uv_layer.data[loop_id]

                        # not active layer and extra UV disabled
                        if not uv_layer.active and self._no_extra_uv:
                            continue

                        egg_vertex_uv = self.make_vertex_uv(uv_layer, uv_loop.uv)
                        if uv_name in uv_tb:
                            t, b, s = uv_tb[uv_name][loop_id]
                            tangent = parent_obj_matrix @ t
                            binormal = parent_obj_matrix @ b
                            egg_vertex_uv.set_tangent(tuple(tangent))
                            egg_vertex_uv.set_binormal(tuple(binormal))
                        egg_vertex.set_uv_obj(egg_vertex_uv)
                        # vertex uv -->

                # generate new ID, add vertex and save last ID
                egg_vertex_id += 1
                egg_vertex_pool.add_vertex(egg_vertex, egg_vertex_id)
                egg_vertex_pool.set_highest_index(egg_vertex_id)
                egg_polygon.add_vertex(egg_vertex)

                # save vertex data for sharing
                if vertex_id not in egg_vertices:
                    egg_vertices[vertex_id] = []
                egg_vertices[vertex_id].append(egg_vertex)

                # attach joints to vertex
                if armature:
                    for vertex_group in vertex.groups:
                        obj_vertex_group = obj.vertex_groups[vertex_group.group]
                        if obj_vertex_group.name in egg_joints:
                            egg_joint = egg_joints[obj_vertex_group.name]
                            egg_joint.set_vertex_membership(
                                egg_vertex, vertex_group.weight)

                # vertex -->

            node.add_child(egg_polygon)
예제 #6
0
    def make_geom(self, gltf_node, gltf_mesh, obj, can_merge=False):
        triangulate = True
        if self._geom_scale != 1:
            obj.scale.x = self._geom_scale
            obj.scale.y = self._geom_scale
            obj.scale.z = self._geom_scale
        apply_modifiers(obj, triangulate=triangulate)
        mesh = obj2mesh(obj, triangulate=triangulate)

        # get or create materials and textures
        gltf_materials = {}
        if not self._no_materials and not is_collision(obj):
            for material in mesh.materials.values():
                # material
                for i, child in enumerate(self._root['materials']):  # existing material
                    if child['name'] == material.name:
                        gltf_materials[material.name] = i
                        break
                else:  # new material
                    gltf_material = self.make_material(material)
                    self._root['materials'].append(gltf_material)
                    gltf_materials[material.name] = len(self._root['materials']) - 1

                # textures
                if not self._no_textures:
                    for type_, gltf_sampler, gltf_image in self.make_textures(material):
                        tname = gltf_image['name']
                        for i, child in enumerate(self._root['images']):  # existing texture
                            if child['name'] == tname:
                                texid = i
                                break
                        else:  # new texture
                            self._root['samplers'].append(gltf_sampler)
                            self._root['images'].append(gltf_image)

                            gltf_texture = {
                                'sampler': len(self._root['samplers']) - 1,
                                'source': len(self._root['images']) - 1,
                            }
                            self._root['textures'].append(gltf_texture)
                            texid = len(self._root['textures']) - 1

                        matid = gltf_materials[material.name]
                        if type(type_) == tuple and len(type_) == 2:
                            type_l1, type_l2 = type_
                            self._root['materials'][matid][type_l1][type_l2] = {'index': texid}
                        else:
                            self._root['materials'][matid][type_] = {'index': texid}

        # get primitives
        gltf_primitives = {}
        gltf_primitive_indices = {}
        if can_merge:
            for i, gltf_primitive in enumerate(gltf_mesh['primitives']):
                mname = None
                if 'material' in gltf_primitive:
                    matid = gltf_primitive['material']
                    mname = self._root['materials'][matid]['name']
                gltf_primitives[mname] = gltf_primitive
                gltf_primitive_indices[mname] = gltf_primitive['extra']['highest_index']

        # get armature and joints
        armature = get_armature(obj)
        max_joints = 0  # get max joints per vertex
        gltf_joints = {}
        if armature:
            max_joints = 1
            for polygon in mesh.polygons:
                for vertex_id in polygon.vertices:
                    vertex = mesh.vertices[vertex_id]
                    joints = 0
                    for vertex_group in vertex.groups:
                        obj_vertex_group = obj.vertex_groups[vertex_group.group]
                        if vertex_group.weight > 0:
                            joints += 1
                    max_joints = max(max_joints, joints)

            if 'skin' in gltf_node:
                gltf_skin = self._root['skins'][gltf_node['skin']]

                # for i, child in enumerate(self._root['skins']):
                #     if child['name'] == armature.name:
                #         gltf_joints = self._get_joints(child)
                #         break
                gltf_joints = self._get_joints(gltf_skin)

        # get max joint layers (4 bones per layer)
        max_joint_layers = math.ceil(max_joints / 4)
        # max_joint_layers = 1

        sharp_vertices = self.get_sharp_vertices(mesh)
        uv_tb = self.get_tangent_bitangent(mesh)
        gltf_vertices = {}
        obj_matrix = self._matrix @ get_object_matrix(obj, armature)

        for polygon in mesh.polygons:
            # <-- polygon
            material = None
            mname = None
            if not self._no_materials:
                try:
                    material = mesh.materials[polygon.material_index]
                    mname = material.name
                except IndexError:
                    pass

            # get or create primitive
            if mname in gltf_primitives:
                gltf_primitive = gltf_primitives[mname]
            else:
                gltf_primitive = self._make_primitive()
                gltf_primitives[mname] = gltf_primitive
                gltf_primitive_indices[mname] = -1
                gltf_mesh['primitives'].append(gltf_primitive)

            # set material
            if material and not self._no_materials and not is_collision(obj):
                if material.name in gltf_materials:
                    gltf_primitive['material'] = gltf_materials[mname]
            # else:
            #     gltf_primitive['material'] = 0

            # vertices
            for i, vertex_id in enumerate(polygon.vertices):
                # i is vertex counter inside a polygon
                # (0, 1, 2) for triangle
                # vertex_id is reusable id,
                # because multiple polygons can share the same vertices

                # <-- vertex
                vertex = mesh.vertices[vertex_id]
                use_smooth = (
                    polygon.use_smooth and
                    vertex_id not in sharp_vertices and
                    not is_collision(obj))

                # try to reuse shared vertices
                if (polygon.use_smooth and
                        vertex_id in gltf_vertices and
                        not is_collision(obj)):
                    shared = False
                    for gltf_vertex in gltf_vertices[vertex_id]:
                        loop_id = polygon.loop_indices[i]
                        gltf_vertex_uv = gltf_vertex[1]
                        if self.can_share_vertex(mesh, loop_id, gltf_vertex_uv):
                            self._buffer.write(
                                gltf_primitive['indices'], gltf_vertex[0])
                            shared = True
                            break
                    if shared:
                        continue

                # make new vertex data
                self.make_vertex(
                    obj_matrix, gltf_primitive, polygon, vertex,
                    use_smooth=use_smooth, can_merge=can_merge)

                # uv layers, active first
                active_uv = 0, 0
                if not is_collision(obj):
                    uv_layers = sorted(
                        mesh.uv_layers.items(), key=lambda x: not x[1].active)
                    for uv_id, (uv_name, uv_layer) in enumerate(uv_layers):
                        # <-- vertex uv
                        loop_id = polygon.loop_indices[i]
                        uv_loop = uv_layer.data[loop_id]

                        # not active layer and extra UV disabled
                        if not uv_layer.active and self._no_extra_uv:
                            continue

                        u, v = uv_loop.uv.to_2d()
                        if uv_layer.active:
                            active_uv = u, v
                        self._write_uv(gltf_primitive, uv_id, u, v)
                        if uv_name in uv_tb and uv_layer.active:
                            self._write_tbs(
                                obj_matrix, gltf_primitive, *uv_tb[uv_name][loop_id],
                                can_merge=can_merge)
                        # vertex uv -->

                # generate new ID, add vertex and save last ID
                gltf_primitive_indices[mname] += 1
                self._buffer.write(
                    gltf_primitive['indices'], gltf_primitive_indices[mname])
                gltf_primitive['extra']['highest_index'] = gltf_primitive_indices[mname]

                # save vertex data for sharing
                if vertex_id not in gltf_vertices:
                    gltf_vertices[vertex_id] = []
                gltf_vertices[vertex_id].append((
                    gltf_primitive_indices[mname], active_uv,
                ))

                # attach joints to vertex
                if gltf_joints:
                    joints_weights = []  # list of vec4
                    vertex_groups = reversed(sorted(
                        vertex.groups, key=lambda vg: vg.weight))
                    for vertex_group in vertex_groups:
                        obj_vertex_group = obj.vertex_groups[vertex_group.group]
                        if (obj_vertex_group.name in gltf_joints and
                                vertex_group.weight > 0):
                            if not joints_weights or len(joints_weights[-1]) >= 4:
                                if len(joints_weights) >= max_joint_layers:
                                    break
                                joints_weights.append([])
                            joint_id = gltf_joints[obj_vertex_group.name]
                            joint_weight = joint_id, vertex_group.weight
                            joints_weights[-1].append(joint_weight)  # push to vec4

                    # padding
                    if joints_weights:
                        while len(joints_weights[-1]) < 4:  # up to vec4
                            joints_weights[-1].append((0, 0))  # push to vec4
                    while len(joints_weights) < max_joint_layers:  # up to max joints
                        vec4 = [(0, 0)] * 4  # make empty vec4
                        joints_weights.append(vec4)

                    assert len(joints_weights) == max_joint_layers
                    self._write_joints_weights(
                        gltf_primitive, len(tuple(gltf_joints.keys())), joints_weights)
예제 #7
0
    def _setup_node(self, node, obj=None, can_merge=False):
        if obj is None:
            return

        armature = get_armature(obj)
        obj_matrix = get_object_matrix(obj, armature=armature)

        # get custom object properties
        obj_props = get_object_properties(obj)

        if not can_merge and not armature:
            if self._geom_scale == 1:
                node.update({
                    'rotation': quat_to_list(obj_matrix.to_quaternion()),
                    'scale': list(obj_matrix.to_scale()),
                    'translation': list(obj_matrix.to_translation()),
                })
            else:
                x, y, z = list(obj_matrix.to_translation())
                x *= self._geom_scale
                y *= self._geom_scale
                z *= self._geom_scale
                node.update({
                    'rotation': quat_to_list(obj_matrix.to_quaternion()),
                    'scale': list(obj_matrix.to_scale()),
                    'translation': [x, y, z],
                })

        # setup collisions
        if not can_merge and is_collision(obj) and obj_props.get('type') != 'Portal':
            collision = {}
            node['extensions'] = {
                'BLENDER_physics': collision,
            }

            # collision shape
            shape = {
                'shapeType': obj.rigid_body.collision_shape,
                'boundingBox': [
                    obj.dimensions[i] / obj_matrix.to_scale()[i]
                    for i in range(3)
                ],
            }
            if obj.rigid_body.collision_shape == 'MESH':
                shape['mesh'] = node.pop('mesh')

            collision['collisionShapes'] = [shape]
            collision['static'] = obj.rigid_body.type == 'PASSIVE'

            # don't actually collide (ghost)
            if (not obj.collision or not obj.collision.use):
                collision['intangible'] = True

        # setup custom properties with tags
        if (obj_props or can_merge) and 'extras' not in node:
            node['extras'] = {}

        for k, v in obj_props.items():
            if node['extras'].get(k):  # tag exists
                tag = node['extras'].get(k)

            if type(v) in (tuple, list, dict):
                tag = json.dumps(v)
            else:
                tag = '{}'.format(v)

            node['extras'][k] = tag

        if can_merge and 'type' not in obj_props:
            node['extras']['type'] = 'Merged'
    def make_geom(self, gltf_node, gltf_mesh, obj, can_merge=False):
        triangulate = True
        if self._geom_scale != 1:
            scale = obj.scale
            obj.scale.x, obj.scale.y, obj.scale.z = [self._geom_scale] * 3
            apply_modifiers(obj, triangulate=triangulate, apply_scale=True)
            obj.scale = scale
        else:
            apply_modifiers(obj, triangulate=triangulate)
        mesh = obj2mesh(obj, triangulate=triangulate)

        # setup shape key names for the primitives
        if mesh.shape_keys:
            for sk_name in sorted(mesh.shape_keys.key_blocks.keys()):
                if sk_name.lower() == 'basis':
                    continue

                gltf_mesh['extras']['targetNames'].append(sk_name)

        # get or create materials and textures
        gltf_materials = {}
        if not self._no_materials and not is_collision(obj):
            for material in mesh.materials.values():
                # material
                for i, child in enumerate(
                        self._root['materials']):  # existing material
                    if child['name'] == material.name:
                        gltf_materials[material.name] = i
                        break
                else:  # new material
                    gltf_material = self.make_material(material)
                    self._root['materials'].append(gltf_material)

                    gltf_materials[material.name] = len(
                        self._root['materials']) - 1

                # textures
                if not self._no_textures:
                    for type_, gltf_sampler, gltf_image in self.make_textures(
                            material):
                        tname = gltf_image['name']
                        for i, child in enumerate(
                                self._root['images']):  # existing texture
                            if child['name'] == tname:
                                texid = i
                                break
                        else:  # new texture
                            self._root['samplers'].append(gltf_sampler)
                            self._root['images'].append(gltf_image)

                            gltf_texture = {
                                'sampler': len(self._root['samplers']) - 1,
                                'source': len(self._root['images']) - 1,
                            }
                            self._root['textures'].append(gltf_texture)
                            texid = len(self._root['textures']) - 1

                        matid = gltf_materials[material.name]
                        if type(type_) == tuple and len(type_) == 2:
                            type_l1, type_l2 = type_
                            self._root['materials'][matid][type_l1][
                                type_l2] = {
                                    'index': texid,
                                    'texCoord': 0,
                                }
                        else:
                            self._root['materials'][matid][type_] = {
                                'index': texid,
                                'texCoord': 0,
                            }

        # get primitives
        gltf_primitives = {}
        gltf_primitive_indices = {}  # splitted vertex buffers
        gltf_mesh_vertices_index = -1  # reusable vertex buffer
        if can_merge:
            for i, gltf_primitive in enumerate(gltf_mesh['primitives']):
                mname = None
                if 'material' in gltf_primitive:
                    matid = gltf_primitive['material']
                    mname = self._root['materials'][matid]['name']
                gltf_primitives[mname] = gltf_primitive
                gltf_primitive_indices[mname] = gltf_primitive['extras'][
                    'highest_index']

        gltf_vertices = {}

        # get armature and joints
        armature = get_armature(obj)
        max_joints = 0  # get max joints per vertex
        gltf_joints = {}
        if armature:
            # max_joints = 1
            # for polygon in mesh.polygons:
            #     for vertex_id in polygon.vertices:
            #         vertex = mesh.vertices[vertex_id]
            #         joints = 0
            #         for vertex_group in vertex.groups:
            #             obj_vertex_group = obj.vertex_groups[vertex_group.group]
            #             if vertex_group.weight > 0:
            #                 joints += 1
            #         max_joints = max(max_joints, joints)

            if 'skin' in gltf_node:
                gltf_skin = self._root['skins'][gltf_node['skin']]

                # for i, child in enumerate(self._root['skins']):
                #     if child['name'] == armature.name:
                #         gltf_joints = self._get_joints(child)
                #         break
                gltf_joints = self._get_joints(gltf_skin)

        # get max joint layers (4 bones per layer)
        # max_joint_layers = math.ceil(max_joints / 4)

        # panda3d-gltf is limited to 1 single layer only (up to 4 bones)
        max_joint_layers = 1

        sharp_vertices = self.get_sharp_vertices(mesh)
        uv_tb = self.get_tangent_bitangent(mesh)
        obj_matrix = self._transform(get_object_matrix(obj, armature=armature))

        for polygon in mesh.polygons:
            # <-- polygon
            material = None
            mname = None
            if not self._no_materials:
                try:
                    material = mesh.materials[polygon.material_index]
                    mname = material.name
                except IndexError:
                    pass

            # get or create primitive
            if mname in gltf_primitives:
                gltf_primitive = gltf_primitives[mname]
            else:
                gltf_primitive = self._make_primitive(gltf_mesh, mesh)
                gltf_primitives[mname] = gltf_primitive
                gltf_primitive_indices[mname] = -1
                gltf_mesh['primitives'].append(gltf_primitive)

            # set material
            if material and not self._no_materials and not is_collision(obj):
                if material.name in gltf_materials:
                    gltf_primitive['material'] = gltf_materials[mname]

            # vertices
            for i, vertex_id in enumerate(polygon.vertices):
                # i is vertex counter inside a polygon
                # (0, 1, 2) for triangle
                # vertex_id is reusable id,
                # because multiple polygons can share the same vertices

                loop_id = polygon.loop_indices[i]

                # <-- vertex
                vertex = mesh.vertices[vertex_id]
                use_smooth = (
                    polygon.use_smooth and
                    # vertex_id not in sharp_vertices and
                    not is_collision(obj))

                # try to reuse shared vertices
                if mname not in gltf_vertices:
                    gltf_vertices[mname] = {}
                if (polygon.use_smooth and vertex_id in gltf_vertices[mname]
                        and not is_collision(obj)):
                    shared = False
                    for gltf_vertex_index, gltf_vertex_uv, gltf_vertex_normal in gltf_vertices[
                            mname][vertex_id]:
                        if self.can_share_vertex(mesh, vertex, loop_id,
                                                 gltf_vertex_uv,
                                                 gltf_vertex_normal):
                            self._buffer.write(gltf_primitive['indices'],
                                               gltf_vertex_index)
                            shared = True
                            break
                    if shared:
                        continue

                # make new vertex data
                can_merge_vertices = can_merge
                if armature:
                    can_merge_vertices = True
                elif is_collision(obj):
                    can_merge_vertices = False
                self.make_vertex(obj_matrix,
                                 gltf_primitive,
                                 mesh,
                                 polygon,
                                 vertex,
                                 vertex_id,
                                 loop_id,
                                 use_smooth=use_smooth,
                                 can_merge=can_merge_vertices)

                # uv layers, active first
                active_uv = 0, 0
                if not is_collision(obj):
                    uv_layers = sorted(mesh.uv_layers.items(),
                                       key=lambda x: not x[1].active)
                    for uv_id, (uv_name, uv_layer) in enumerate(uv_layers):
                        # <-- vertex uv
                        uv_loop = uv_layer.data[loop_id]

                        # not active layer and extra UV disabled
                        if not uv_layer.active and self._no_extra_uv:
                            continue

                        u, v = uv_loop.uv.to_2d()
                        if uv_layer.active:
                            active_uv = u, v
                        self._write_uv(gltf_primitive, uv_id, u, v)
                        if uv_name in uv_tb and uv_layer.active:
                            self._write_tbs(obj_matrix,
                                            gltf_primitive,
                                            *uv_tb[uv_name][loop_id],
                                            can_merge=can_merge_vertices)
                        # vertex uv -->

                # generate new ID, add vertex and save last ID
                gltf_primitive_indices[mname] += 1
                gltf_mesh_vertices_index += 1
                if self._split_primitives:
                    idx = gltf_primitive_indices[mname]
                else:
                    idx = gltf_mesh_vertices_index
                self._buffer.write(gltf_primitive['indices'], idx)
                gltf_primitive['extras'][
                    'highest_index'] = gltf_primitive_indices[mname]

                # save vertex data for sharing
                if vertex_id not in gltf_vertices[mname]:
                    gltf_vertices[mname][vertex_id] = []
                gltf_vertices[mname][vertex_id].append((
                    idx,
                    active_uv,
                    mesh.loops[loop_id].normal
                    if use_smooth else polygon.normal,
                ))

                # attach joints to vertex
                if gltf_joints:
                    joints_weights = []
                    vertex_groups = reversed(
                        sorted(vertex.groups, key=lambda vg: vg.weight))
                    for vertex_group in vertex_groups:
                        obj_vertex_group = obj.vertex_groups[
                            vertex_group.group]

                        # no bones with vertex group's name
                        if obj_vertex_group.name not in gltf_joints:
                            continue

                        # weight is zero
                        if vertex_group.weight <= 0:
                            continue

                        joint_id = gltf_joints[obj_vertex_group.name]
                        joints_weights.append([joint_id, vertex_group.weight])

                    # padding
                    while ((len(joints_weights) % 4 != 0)
                           or (len(joints_weights) < max_joint_layers * 4)):
                        joints_weights.append((0, 0))

                    # limit by max joints
                    joints_weights = joints_weights[:max_joint_layers * 4]

                    imax = -1
                    wmax = 0
                    for i, (joint, weight) in enumerate(joints_weights):
                        if weight > wmax:
                            imax = i
                            wmax = weight
                    # if imax >= 0:
                    #     joints_weights[imax][1] += 1 - sum(list(zip(*joints_weights))[1])

                    # group by 4 joint-weight pairs
                    joints_weights_groups = []
                    for i in range(len(joints_weights) // 4):
                        group = joints_weights[i * 4:i * 4 + 4]
                        joints_weights_groups.append(group)

                    self._write_joints_weights(gltf_primitive,
                                               len(tuple(gltf_joints.keys())),
                                               joints_weights_groups)