@ -365,6 +385,9 @@ class MeshConverter(_MeshManager):
fordspaninloc.values():
log_msg("[DrawableSpans '{}']",dspan.key.name)
# We do one last pass to register bones.
self._register_bones_before_merge(dspan)
# This mega-function does a lot:
# 1. Converts SourceSpans (geospans) to Icicles and bakes geometry into plGBuffers
# 2. Calculates the Icicle bounds
@ -373,6 +396,87 @@ class MeshConverter(_MeshManager):
dspan.composeGeometry(True,True)
inc_progress()
def_register_bones_before_merge(self,dspan):
# We export all armatures used by this DSpan only once, unless an object uses multiple armatures - in which case, we treat the list of armatures as a single armature:
# [Armature 0: bone A, bone B, bone C...]
# [Armature 1: bone D, bone E, bone F...]
# [Armature 0, armature 1: bone A, bone B, bone C... bone D, bone E, bone F...]
# But really, the latter case should NEVER happen because users never use more than one armature modifier, cm'on.
# NOTE: we will export all bones, even those that are not used by any vertices in the DSpan. Would be too complex otherwise.
# NOTE: DSpan bone transforms are shared between all drawables in this dspan. This implies all skinned meshes must share the same
# coordinate system - and that's easier if these objects simply don't have a coordinate interface.
# Hence, we forbid skinned objects from having a coordint.
# The alternative is exporting each bone once per drawable with different l2w/w2l/l2b/b2l, but this is more complex
# and there is no good reason to do so - this just increases the risk of deformations going crazy due to a misaligned object.
# And retrieve the vertex groups that are deformed by an armature.
armatures=self._objects_armatures.get(bo.name)
export_deform=armaturesisnotNone
ifexport_deform:
# We will need to remap IDs of each bone per armature usage. This is annoying, especially since we support multiple armatures...
i=1
all_bone_names={}
forarmatureinarmatures:
forboneinarmature.data.bones:
all_bone_names[bone.name]=i
i+=1
# This will map the group ID (used by Blender vertices) to the bone index exported to Plasma.
# Theoretically supports multiple armatures, except if the two armatures have the same bone names (because that would be REALLY asking for a lot here).
# If the bone is not found, we'll just map to the null bone.
# We will also need to know which bones deform the most vertices per material, for max/pen bones.
forgdingeodata.values():
gd.total_weight_by_bones={j:0.0forjinrange(i)}
warned_extra_bone_weights=False
# Convert Blender faces into things we can stuff into libHSPlasma
fori,tessfaceinenumerate(mesh.tessfaces):
data=geodata.get(tessface.material_index)
@ -484,6 +608,37 @@ class MeshConverter(_MeshManager):
uvs.append(dPosDv)
geoVertex.uvs=uvs
ifexport_deform:
# Get bone ID and weight from the vertex' "group" data.
# While we're at it, sort it by weight, and filter groups that
# have no bone assigned. Take only the first 3 bones.
weights=sorted([ \
(group_id_to_bone_id[group.group],group.weight) \
forgroupinsource.groups \
ifgroup.weight>0 \
],key=lambdat:t[1],reverse=True)
iflen(weights)>3andnotwarned_extra_bone_weights:
warned_extra_bone_weights=True
self._report.warn(f"'{bo.name}': only three bones can deform a vertex at a time. Please use Weight Tools -> Limit Total at 3 to ensure deformation is consistent between Blender and Plasma.")
weights=weights[:3]
total_weight=sum((w[1]forwinweights))
# NOTE: Blender will ALWAYS normalize bone weights when deforming !
# This means if weights don't add up to 1, we CANNOT assign the remaining weight to the null bone.
# For instance, a vertex with a single bone of weight 0.25 will move as if it were weighted 1.0.
# However, a vertex with no bone at all will not move at all (null bone).