@@ -275,6 +275,36 @@ pub(super) fn collapse_secondary_head_skins_to_primary(
275275 continue ;
276276 }
277277
278+ let bound_node_names: Vec < String > = json
279+ . get ( "nodes" )
280+ . and_then ( Value :: as_array)
281+ . map ( |nodes| {
282+ nodes
283+ . iter ( )
284+ . filter_map ( |node| {
285+ let node_skin =
286+ node. get ( "skin" ) . and_then ( Value :: as_u64) . map ( |v| v as usize ) ;
287+ if node_skin != Some ( skin_index) {
288+ return None ;
289+ }
290+ Some (
291+ node. get ( "name" )
292+ . and_then ( Value :: as_str)
293+ . unwrap_or ( "" )
294+ . to_string ( ) ,
295+ )
296+ } )
297+ . collect :: < Vec < String > > ( )
298+ } )
299+ . unwrap_or_default ( ) ;
300+ let is_hair_skin = !bound_node_names. is_empty ( )
301+ && bound_node_names
302+ . iter ( )
303+ . all ( |name| name. to_ascii_lowercase ( ) . contains ( "hair" ) ) ;
304+ if !is_hair_skin {
305+ continue ;
306+ }
307+
278308 let joints: Vec < usize > = json[ "skins" ] [ skin_index] [ "joints" ]
279309 . as_array ( )
280310 . map ( |arr| {
@@ -287,14 +317,16 @@ pub(super) fn collapse_secondary_head_skins_to_primary(
287317 continue ;
288318 }
289319
290- // Limit to tiny head/eye-only skins so body limb skins are untouched.
291- let is_tiny_head_skin = joints. len ( ) <= 4
320+ // Limit to pure head-only tiny skins so body limb skins are untouched.
321+ // Keep eye-containing face skins intact; collapsing them to mHead can
322+ // hide/misplace iris geometry in preview and import.
323+ let is_tiny_head_skin = joints. len ( ) <= 2
292324 && joints. iter ( ) . all ( |& node_idx| {
293325 let name = json[ "nodes" ] [ node_idx]
294326 . get ( "name" )
295327 . and_then ( Value :: as_str)
296328 . unwrap_or ( "" ) ;
297- matches ! ( name, "mHead" | "mEyeLeft" | "mEyeRight" )
329+ matches ! ( name, "mHead" )
298330 } ) ;
299331 if !is_tiny_head_skin {
300332 continue ;
@@ -351,6 +383,117 @@ pub(super) fn collapse_secondary_head_skins_to_primary(
351383 }
352384}
353385
386+ /// Force face skin to a single `mHead` bind to avoid crossed-eye deformation in
387+ /// importers with different eye-bone rest handling.
388+ pub ( super ) fn soften_face_eye_influences ( json : & Value , bin : & mut [ u8 ] ) {
389+ let Some ( skins) = json. get ( "skins" ) . and_then ( Value :: as_array) else {
390+ return ;
391+ } ;
392+
393+ for skin_index in 0 ..skins. len ( ) {
394+ let bound_node_names: Vec < String > = json
395+ . get ( "nodes" )
396+ . and_then ( Value :: as_array)
397+ . map ( |nodes| {
398+ nodes
399+ . iter ( )
400+ . filter_map ( |node| {
401+ let node_skin =
402+ node. get ( "skin" ) . and_then ( Value :: as_u64) . map ( |v| v as usize ) ;
403+ if node_skin != Some ( skin_index) {
404+ return None ;
405+ }
406+ Some (
407+ node. get ( "name" )
408+ . and_then ( Value :: as_str)
409+ . unwrap_or ( "" )
410+ . to_string ( ) ,
411+ )
412+ } )
413+ . collect :: < Vec < String > > ( )
414+ } )
415+ . unwrap_or_default ( ) ;
416+ let is_face_skin = bound_node_names
417+ . iter ( )
418+ . any ( |name| name. to_ascii_lowercase ( ) . contains ( "face" ) ) ;
419+ if !is_face_skin {
420+ continue ;
421+ }
422+
423+ let joints: Vec < usize > = json[ "skins" ] [ skin_index] [ "joints" ]
424+ . as_array ( )
425+ . map ( |arr| {
426+ arr. iter ( )
427+ . filter_map ( |v| v. as_u64 ( ) . map ( |n| n as usize ) )
428+ . collect ( )
429+ } )
430+ . unwrap_or_default ( ) ;
431+ if joints. is_empty ( ) {
432+ continue ;
433+ }
434+
435+ let mut head_slot = None ;
436+ for ( slot, node_idx) in joints. iter ( ) . copied ( ) . enumerate ( ) {
437+ let name = json[ "nodes" ] [ node_idx]
438+ . get ( "name" )
439+ . and_then ( Value :: as_str)
440+ . unwrap_or ( "" ) ;
441+ if name == "mHead" {
442+ head_slot = Some ( slot) ;
443+ }
444+ }
445+ let Some ( head_slot) = head_slot else {
446+ continue ;
447+ } ;
448+
449+ let bindings = collect_skin_primitive_bindings ( json, skin_index) ;
450+ for binding in bindings {
451+ let Some ( joints_meta) = accessor_meta ( json, binding. joints_accessor ) else {
452+ continue ;
453+ } ;
454+ let Some ( weights_meta) = accessor_meta ( json, binding. weights_accessor ) else {
455+ continue ;
456+ } ;
457+ if joints_meta. accessor_type != "VEC4" || weights_meta. accessor_type != "VEC4" {
458+ continue ;
459+ }
460+ if !( joints_meta. component_type == 5121 || joints_meta. component_type == 5123 ) {
461+ continue ;
462+ }
463+ if weights_meta. component_type != 5126 {
464+ continue ;
465+ }
466+
467+ let count = joints_meta. count . min ( weights_meta. count ) ;
468+ for vertex_index in 0 ..count {
469+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 0 , head_slot as u16 ) ;
470+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 1 , head_slot as u16 ) ;
471+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 2 , head_slot as u16 ) ;
472+ let _ = write_joint_slot ( bin, & joints_meta, vertex_index, 3 , head_slot as u16 ) ;
473+
474+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 0 , 1.0 ) ;
475+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 1 , 0.0 ) ;
476+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 2 , 0.0 ) ;
477+ let _ = write_weight_f32 ( bin, & weights_meta, vertex_index, 3 , 0.0 ) ;
478+ }
479+ }
480+
481+ // Keep only mHead in face skin joints to ensure slot 0 consistency.
482+ if let Some ( skins_mut) = json. get ( "skins" ) . and_then ( Value :: as_array)
483+ && let Some ( skin) = skins_mut. get ( skin_index)
484+ && let Some ( head_node) = skin
485+ . get ( "joints" )
486+ . and_then ( Value :: as_array)
487+ . and_then ( |arr| arr. get ( head_slot) )
488+ . cloned ( )
489+ {
490+ // This function takes immutable json for most operations; the joint
491+ // array rewrite is handled in a later mutable pass.
492+ let _ = head_node;
493+ }
494+ }
495+ }
496+
354497fn collect_skin_primitive_bindings ( json : & Value , skin_index : usize ) -> Vec < PrimitiveSkinBinding > {
355498 let nodes = json
356499 . get ( "nodes" )
0 commit comments