glTF exporter: Manage skinning when some vertices are not weights at all
This commit is contained in:
parent
1d5c8b54ee
commit
564fdcdf71
|
@ -4,7 +4,7 @@
|
|||
bl_info = {
|
||||
'name': 'glTF 2.0 format',
|
||||
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||
"version": (3, 2, 16),
|
||||
"version": (3, 2, 17),
|
||||
'blender': (3, 1, 0),
|
||||
'location': 'File > Import-Export',
|
||||
'description': 'Import-Export as glTF 2.0',
|
||||
|
|
|
@ -82,7 +82,14 @@ def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups
|
|||
|
||||
locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings)
|
||||
if skin:
|
||||
vert_bones, num_joint_sets = __get_bone_data(blender_mesh, skin, blender_vertex_groups)
|
||||
vert_bones, num_joint_sets, need_neutral_bone = __get_bone_data(blender_mesh, skin, blender_vertex_groups)
|
||||
if need_neutral_bone is True:
|
||||
# Need to create a fake joint at root of armature
|
||||
# In order to assign not assigned vertices to it
|
||||
# But for now, this is not yet possible, we need to wait the armature node is created
|
||||
# Just store this, to be used later
|
||||
armature_uuid = export_settings['vtree'].nodes[uuid_for_skined_data].armature
|
||||
export_settings['vtree'].nodes[armature_uuid].need_neutral_bone = True
|
||||
|
||||
# In Blender there is both per-vert data, like position, and also per-loop
|
||||
# (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert
|
||||
|
@ -535,6 +542,9 @@ def __get_colors(blender_mesh, color_i):
|
|||
|
||||
|
||||
def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
|
||||
|
||||
need_neutral_bone = False
|
||||
|
||||
joint_name_to_index = {joint.name: index for index, joint in enumerate(skin.joints)}
|
||||
group_to_joint = [joint_name_to_index.get(g.name) for g in blender_vertex_groups]
|
||||
|
||||
|
@ -557,7 +567,10 @@ def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
|
|||
continue
|
||||
bones.append((joint, weight))
|
||||
bones.sort(key=lambda x: x[1], reverse=True)
|
||||
if not bones: bones = ((0, 1.0),) # HACK for verts with zero weight (#308)
|
||||
if not bones:
|
||||
# Is not assign to any bone
|
||||
bones = ((len(skin.joints), 1.0),) # Assign to a joint that will be created later
|
||||
need_neutral_bone = True
|
||||
vert_bones.append(bones)
|
||||
if len(bones) > max_num_influences:
|
||||
max_num_influences = len(bones)
|
||||
|
@ -565,7 +578,7 @@ def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
|
|||
# How many joint sets do we need? 1 set = 4 influences
|
||||
num_joint_sets = (max_num_influences + 3) // 4
|
||||
|
||||
return vert_bones, num_joint_sets
|
||||
return vert_bones, num_joint_sets, need_neutral_bone
|
||||
|
||||
|
||||
def __zup2yup(array):
|
||||
|
|
|
@ -68,6 +68,8 @@ def __gather_scene(blender_scene, export_settings):
|
|||
if node is not None:
|
||||
scene.nodes.append(node)
|
||||
|
||||
vtree.add_neutral_bones()
|
||||
|
||||
export_user_extensions('gather_scene_hook', export_settings, scene, blender_scene)
|
||||
|
||||
return scene
|
||||
|
|
|
@ -73,6 +73,9 @@ def __gather_inverse_bind_matrices(armature_uuid, export_settings):
|
|||
axis_basis_change = mathutils.Matrix(
|
||||
((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
|
||||
|
||||
# store matrix_world of armature in case we need to add a neutral bone
|
||||
export_settings['vtree'].nodes[armature_uuid].matrix_world_armature = blender_armature_object.matrix_world.copy()
|
||||
|
||||
bones_uuid = export_settings['vtree'].get_all_bones(armature_uuid)
|
||||
def __collect_matrices(bone):
|
||||
inverse_bind_matrix = (
|
||||
|
|
|
@ -3,10 +3,17 @@
|
|||
|
||||
import bpy
|
||||
import uuid
|
||||
import numpy as np
|
||||
|
||||
from . import gltf2_blender_export_keys
|
||||
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
|
||||
from mathutils import Quaternion, Matrix
|
||||
from io_scene_gltf2.io.com import gltf2_io
|
||||
from io_scene_gltf2.io.imp.gltf2_io_binary import BinaryData
|
||||
from io_scene_gltf2.io.com import gltf2_io_constants
|
||||
from .gltf2_blender_gather_primitive_attributes import array_to_accessor
|
||||
from io_scene_gltf2.io.exp import gltf2_io_binary_data
|
||||
from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
|
||||
|
||||
class VExportNode:
|
||||
|
||||
|
@ -375,6 +382,68 @@ class VExportTree:
|
|||
n.armature = candidates[0].uuid
|
||||
del n.armature_needed
|
||||
|
||||
def add_neutral_bones(self):
|
||||
for n in [n for n in self.nodes.values() if n.armature is not None and n.blender_type == VExportNode.OBJECT and hasattr(self.nodes[n.armature], "need_neutral_bone")]: #all skin meshes objects where neutral bone is needed
|
||||
# First add a new node
|
||||
|
||||
axis_basis_change = Matrix.Identity(4)
|
||||
if self.export_settings[gltf2_blender_export_keys.YUP]:
|
||||
axis_basis_change = Matrix(((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
|
||||
|
||||
trans, rot, sca = axis_basis_change.decompose()
|
||||
translation, rotation, scale = (None, None, None)
|
||||
if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
|
||||
translation = [trans[0], trans[1], trans[2]]
|
||||
if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0:
|
||||
rotation = [rot[1], rot[2], rot[3], rot[0]]
|
||||
if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
|
||||
scale = [sca[0], sca[1], sca[2]]
|
||||
neutral_bone = gltf2_io.Node(
|
||||
camera=None,
|
||||
children=None,
|
||||
extensions=None,
|
||||
extras=None,
|
||||
matrix=None,
|
||||
mesh=None,
|
||||
name='neutral_bone',
|
||||
rotation=rotation,
|
||||
scale=scale,
|
||||
skin=None,
|
||||
translation=translation,
|
||||
weights=None
|
||||
)
|
||||
# Add it to child list of armature
|
||||
self.nodes[n.armature].node.children.append(neutral_bone)
|
||||
# Add it to joint list
|
||||
n.node.skin.joints.append(neutral_bone)
|
||||
|
||||
# Need to add an InverseBindMatrix
|
||||
array = BinaryData.decode_accessor_internal(n.node.skin.inverse_bind_matrices)
|
||||
|
||||
axis_basis_change = Matrix.Identity(4)
|
||||
if self.export_settings[gltf2_blender_export_keys.YUP]:
|
||||
axis_basis_change = Matrix(
|
||||
((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
|
||||
|
||||
inverse_bind_matrix = (
|
||||
axis_basis_change @ self.nodes[n.armature].matrix_world_armature).inverted_safe()
|
||||
|
||||
matrix = []
|
||||
for column in range(0, 4):
|
||||
for row in range(0, 4):
|
||||
matrix.append(inverse_bind_matrix[row][column])
|
||||
|
||||
array = np.append(array, np.array([matrix]), axis=0)
|
||||
binary_data = gltf2_io_binary_data.BinaryData.from_list(array.flatten(), gltf2_io_constants.ComponentType.Float)
|
||||
n.node.skin.inverse_bind_matrices = gltf2_blender_gather_accessors.gather_accessor(
|
||||
binary_data,
|
||||
gltf2_io_constants.ComponentType.Float,
|
||||
len(array.flatten()) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Mat4),
|
||||
None,
|
||||
None,
|
||||
gltf2_io_constants.DataType.Mat4,
|
||||
self.export_settings
|
||||
)
|
||||
def get_unused_skins(self):
|
||||
from .gltf2_blender_gather_skins import gather_skin
|
||||
skins = []
|
||||
|
|
|
@ -77,6 +77,37 @@ class BinaryData():
|
|||
|
||||
return array
|
||||
|
||||
|
||||
@staticmethod
|
||||
def decode_accessor_internal(accessor):
|
||||
# Is use internally when accessor binary data is not yet in a glTF buffer_view
|
||||
# MAT2/3 have special alignment requirements that aren't handled. But it
|
||||
# doesn't matter because nothing uses them.
|
||||
assert accessor.type not in ['MAT2', 'MAT3']
|
||||
|
||||
dtype = ComponentType.to_numpy_dtype(accessor.component_type)
|
||||
component_nb = DataType.num_elements(accessor.type)
|
||||
|
||||
buffer_data = accessor.buffer_view.data
|
||||
|
||||
accessor_offset = accessor.byte_offset or 0
|
||||
buffer_data = buffer_data[accessor_offset:]
|
||||
|
||||
bytes_per_elem = dtype(1).nbytes
|
||||
default_stride = bytes_per_elem * component_nb
|
||||
stride = default_stride
|
||||
|
||||
array = np.frombuffer(
|
||||
buffer_data,
|
||||
dtype=np.dtype(dtype).newbyteorder('<'),
|
||||
count=accessor.count * component_nb,
|
||||
)
|
||||
array = array.reshape(accessor.count, component_nb)
|
||||
|
||||
return array
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def decode_accessor_obj(gltf, accessor):
|
||||
# MAT2/3 have special alignment requirements that aren't handled. But it
|
||||
|
|
Loading…
Reference in New Issue