@@ -226,6 +226,131 @@ pub(super) fn optimize_skinning_weights_and_joints(json: &mut Value, bin: &mut [
226226 Ok ( ( ) )
227227}
228228
229+ /// Stabilize tiny head/eye-only secondary skins by forcing a single `mHead`
230+ /// bind inside each skin.
231+ ///
232+ /// This avoids importer instability with tiny facial skins that include eye
233+ /// joints while keeping skin-index topology unchanged.
234+ pub ( super ) fn collapse_secondary_head_skins_to_primary (
235+ json : & mut Value ,
236+ bin : & mut [ u8 ] ,
237+ humanoid_bone_nodes : & HashMap < String , usize > ,
238+ ) {
239+ let Some ( skins) = json. get ( "skins" ) . and_then ( Value :: as_array) else {
240+ return ;
241+ } ;
242+ if skins. len ( ) <= 1 {
243+ return ;
244+ }
245+
246+ let primary_skin_index = skins
247+ . iter ( )
248+ . enumerate ( )
249+ . max_by_key ( |( _, skin) | {
250+ skin. get ( "joints" )
251+ . and_then ( Value :: as_array)
252+ . map ( |j| j. len ( ) )
253+ . unwrap_or ( 0 )
254+ } )
255+ . map ( |( i, _) | i) ;
256+ let Some ( primary_skin_index) = primary_skin_index else {
257+ return ;
258+ } ;
259+
260+ let Some ( head_node_index) = humanoid_bone_nodes. get ( "head" ) . copied ( ) else {
261+ return ;
262+ } ;
263+
264+ let mut used_skin_indices = HashSet :: < usize > :: new ( ) ;
265+ if let Some ( nodes) = json. get ( "nodes" ) . and_then ( Value :: as_array) {
266+ for node in nodes {
267+ if let Some ( si) = node. get ( "skin" ) . and_then ( Value :: as_u64) . map ( |v| v as usize ) {
268+ used_skin_indices. insert ( si) ;
269+ }
270+ }
271+ }
272+
273+ for skin_index in 0 ..skins. len ( ) {
274+ if skin_index == primary_skin_index || !used_skin_indices. contains ( & skin_index) {
275+ continue ;
276+ }
277+
278+ let joints: Vec < usize > = json[ "skins" ] [ skin_index] [ "joints" ]
279+ . as_array ( )
280+ . map ( |arr| {
281+ arr. iter ( )
282+ . filter_map ( |v| v. as_u64 ( ) . map ( |n| n as usize ) )
283+ . collect ( )
284+ } )
285+ . unwrap_or_default ( ) ;
286+ if joints. is_empty ( ) {
287+ continue ;
288+ }
289+
290+ // Limit to tiny head/eye-only skins so body limb skins are untouched.
291+ let is_tiny_head_skin = joints. len ( ) <= 4
292+ && joints. iter ( ) . all ( |& node_idx| {
293+ let name = json[ "nodes" ] [ node_idx]
294+ . get ( "name" )
295+ . and_then ( Value :: as_str)
296+ . unwrap_or ( "" ) ;
297+ matches ! ( name, "mHead" | "mEyeLeft" | "mEyeRight" )
298+ } ) ;
299+ if !is_tiny_head_skin {
300+ continue ;
301+ }
302+
303+ let bindings = collect_skin_primitive_bindings ( json, skin_index) ;
304+ for binding in bindings {
305+ let Some ( joints_meta) = accessor_meta ( json, binding. joints_accessor ) else {
306+ continue ;
307+ } ;
308+ let Some ( weights_meta) = accessor_meta ( json, binding. weights_accessor ) else {
309+ continue ;
310+ } ;
311+ if joints_meta. accessor_type != "VEC4" || weights_meta. accessor_type != "VEC4" {
312+ continue ;
313+ }
314+ if !( joints_meta. component_type == 5121 || joints_meta. component_type == 5123 ) {
315+ continue ;
316+ }
317+ if weights_meta. component_type != 5126 {
318+ continue ;
319+ }
320+
321+ let count = joints_meta. count . min ( weights_meta. count ) ;
322+ for vertex_index in 0 ..count {
323+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 0 , 0 ) ;
324+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 1 , 0 ) ;
325+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 2 , 0 ) ;
326+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 3 , 0 ) ;
327+
328+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 0 , 1.0 ) ;
329+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 1 , 0.0 ) ;
330+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 2 , 0.0 ) ;
331+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 3 , 0.0 ) ;
332+ }
333+ }
334+
335+ // Rewrite this skin to a single-joint mHead bind.
336+ if let Some ( skins_mut) = json. get_mut ( "skins" ) . and_then ( Value :: as_array_mut)
337+ && let Some ( skin_mut) = skins_mut. get_mut ( skin_index)
338+ {
339+ skin_mut[ "joints" ] = Value :: Array ( vec ! [ Value :: from( head_node_index as u64 ) ] ) ;
340+
341+ if let Some ( acc_idx) = skin_mut
342+ . get ( "inverseBindMatrices" )
343+ . and_then ( Value :: as_u64)
344+ . map ( |v| v as usize )
345+ && let Some ( accessors) = json. get_mut ( "accessors" ) . and_then ( Value :: as_array_mut)
346+ && let Some ( accessor) = accessors. get_mut ( acc_idx)
347+ {
348+ accessor[ "count" ] = Value :: from ( 1u64 ) ;
349+ }
350+ }
351+ }
352+ }
353+
229354fn collect_skin_primitive_bindings ( json : & Value , skin_index : usize ) -> Vec < PrimitiveSkinBinding > {
230355 let nodes = json
231356 . get ( "nodes" )
0 commit comments