2022-09-09 11:40:52 +08:00
/ *
* MIT License
2023-08-05 14:47:03 +08:00
*
2022-09-09 11:40:52 +08:00
* Copyright ( c ) 2022 bd_
2023-08-05 14:47:03 +08:00
*
2022-09-09 11:40:52 +08:00
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the "Software" ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
2023-08-05 14:47:03 +08:00
*
2022-09-09 11:40:52 +08:00
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software .
2023-08-05 14:47:03 +08:00
*
2022-09-09 11:40:52 +08:00
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
* /
2024-03-03 16:34:48 +08:00
#region
2022-10-05 10:43:46 +08:00
using System ;
2022-09-12 05:53:25 +08:00
using System.Collections.Generic ;
2024-11-26 11:09:12 +08:00
using System.Collections.Immutable ;
using System.Linq ;
2023-09-24 15:59:43 +08:00
using nadena.dev.modular_avatar.core.armature_lock ;
2022-08-28 04:38:52 +08:00
using UnityEngine ;
2023-09-24 15:59:43 +08:00
using UnityEngine.Serialization ;
2022-08-28 04:38:52 +08:00
2024-03-03 16:34:48 +08:00
#endregion
2022-11-11 12:39:58 +08:00
namespace nadena.dev.modular_avatar.core
2022-08-28 04:38:52 +08:00
{
2023-09-24 15:59:43 +08:00
[Serializable]
public enum ArmatureLockMode
{
Legacy ,
NotLocked ,
BaseToMerge ,
BidirectionalExact
}
2022-08-30 06:13:26 +08:00
[ExecuteInEditMode]
2022-10-20 10:42:33 +08:00
[DisallowMultipleComponent]
2022-11-10 09:49:00 +08:00
[AddComponentMenu("Modular Avatar/MA Merge Armature")]
2023-10-11 19:40:26 +08:00
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-armature?lang=auto")]
2024-09-02 08:29:58 +08:00
public class ModularAvatarMergeArmature : AvatarTagComponent , IHaveObjReferences
2022-08-28 04:38:52 +08:00
{
2024-11-26 11:09:12 +08:00
// Injected by HeuristicBoneMapper
internal static Func < string , string > NormalizeBoneName ;
internal static ImmutableHashSet < string > AllBoneNames ;
2023-07-29 17:04:59 +08:00
public AvatarObjectReference mergeTarget = new AvatarObjectReference ( ) ;
2022-10-03 09:16:58 +08:00
public GameObject mergeTargetObject = > mergeTarget . Get ( this ) ;
2023-07-29 17:04:59 +08:00
public string prefix = "" ;
public string suffix = "" ;
2023-09-24 15:59:43 +08:00
[FormerlySerializedAs("locked")] public bool legacyLocked ;
public ArmatureLockMode LockMode = ArmatureLockMode . Legacy ;
2023-07-29 17:04:59 +08:00
public bool mangleNames = true ;
2022-08-30 06:13:26 +08:00
2024-11-03 06:17:24 +08:00
// Inserted from HeuristicBoneMapper(Editor Assembly) with InitializeOnLoadMethod
// We use raw `boneNamePatterns` instead of `BoneToNameMap` because BoneToNameMap requires matching with normalized bone name, but normalizing makes raw prefix/suffix unavailable.
internal static string [ ] [ ] boneNamePatterns ;
2023-09-24 15:59:43 +08:00
private ArmatureLockController _lockController ;
2024-02-27 18:02:04 +08:00
internal Transform MapBone ( Transform bone )
{
var relPath = RuntimeUtil . RelativePath ( gameObject , bone . gameObject ) ;
if ( relPath = = null ) throw new ArgumentException ( "Bone is not a child of this component" ) ;
if ( relPath = = "" ) return mergeTarget . Get ( this ) . transform ;
var segments = relPath . Split ( '/' ) ;
var pointer = mergeTarget . Get ( this ) . transform ;
foreach ( var segment in segments )
{
2024-03-03 18:19:53 +08:00
if ( ! segment . StartsWith ( prefix ) | | ! segment . EndsWith ( suffix )
| | segment . Length = = prefix . Length + suffix . Length ) return null ;
2024-02-27 18:02:04 +08:00
var targetObjectName = segment . Substring ( prefix . Length ,
segment . Length - prefix . Length - suffix . Length ) ;
pointer = pointer . Find ( targetObjectName ) ;
}
return pointer ;
}
2023-09-24 15:59:43 +08:00
internal Transform FindCorrespondingBone ( Transform bone , Transform baseParent )
2022-09-12 05:53:25 +08:00
{
2023-09-24 15:59:43 +08:00
var childName = bone . gameObject . name ;
2022-10-03 09:16:58 +08:00
2024-03-03 18:19:53 +08:00
if ( ! childName . StartsWith ( prefix ) | | ! childName . EndsWith ( suffix )
| | childName . Length = = prefix . Length + suffix . Length ) return null ;
2023-09-24 15:59:43 +08:00
var targetObjectName = childName . Substring ( prefix . Length ,
childName . Length - prefix . Length - suffix . Length ) ;
return baseParent . Find ( targetObjectName ) ;
2022-09-12 05:53:25 +08:00
}
2023-01-19 20:32:44 +08:00
protected override void OnValidate ( )
2022-08-28 04:38:52 +08:00
{
2023-01-19 20:32:44 +08:00
base . OnValidate ( ) ;
2023-09-24 15:59:43 +08:00
MigrateLockConfig ( ) ;
RuntimeUtil . delayCall ( SetLockMode ) ;
}
2023-01-19 20:32:44 +08:00
2023-11-09 21:06:18 +08:00
internal void ResetArmatureLock ( )
{
if ( _lockController ! = null )
{
_lockController . Dispose ( ) ;
_lockController = null ;
}
SetLockMode ( ) ;
}
2024-03-03 16:34:48 +08:00
internal void SetLockMode ( )
2023-09-24 15:59:43 +08:00
{
2023-09-24 16:16:23 +08:00
if ( this = = null ) return ;
2023-09-24 15:59:43 +08:00
if ( _lockController = = null )
2022-08-28 04:38:52 +08:00
{
2023-09-24 15:59:43 +08:00
_lockController = ArmatureLockController . ForMerge ( this , GetBonesForLock ) ;
2024-03-03 16:34:48 +08:00
_lockController . WhenUnstable + = OnUnstableLock ;
2023-09-24 15:59:43 +08:00
}
2022-08-30 06:13:26 +08:00
2024-03-03 16:34:48 +08:00
_lockController . Mode = LockMode ;
2023-09-24 15:59:43 +08:00
2024-02-17 18:20:08 +08:00
_lockController . Enabled = enabled ;
2023-09-24 15:59:43 +08:00
}
2024-03-03 16:34:48 +08:00
private void OnUnstableLock ( )
{
_lockController . Mode = LockMode = ArmatureLockMode . NotLocked ;
}
2023-09-24 15:59:43 +08:00
private void MigrateLockConfig ( )
{
if ( LockMode = = ArmatureLockMode . Legacy )
{
LockMode = legacyLocked ? ArmatureLockMode . BidirectionalExact : ArmatureLockMode . BaseToMerge ;
}
2022-08-28 04:38:52 +08:00
}
2022-08-30 06:13:26 +08:00
2022-09-12 05:53:25 +08:00
private void OnEnable ( )
2022-08-30 06:13:26 +08:00
{
2023-09-24 15:59:43 +08:00
MigrateLockConfig ( ) ;
SetLockMode ( ) ;
2022-09-12 05:53:25 +08:00
}
private void OnDisable ( )
{
2024-02-17 18:20:08 +08:00
// we use enabled instead of activeAndEnabled to ensure we track even when the GameObject is disabled
_lockController . Enabled = enabled ;
2022-09-12 05:53:25 +08:00
}
2023-07-30 00:15:16 +08:00
protected override void OnDestroy ( )
2022-09-12 05:53:25 +08:00
{
2023-07-30 00:15:16 +08:00
base . OnDestroy ( ) ;
2023-09-24 15:59:43 +08:00
_lockController ? . Dispose ( ) ;
_lockController = null ;
2022-09-12 05:53:25 +08:00
}
2023-09-24 13:44:07 +08:00
public override void ResolveReferences ( )
2023-08-05 14:47:03 +08:00
{
mergeTarget ? . Get ( this ) ;
}
2023-09-24 15:59:43 +08:00
private List < ( Transform , Transform ) > GetBonesForLock ( )
2022-09-12 05:53:25 +08:00
{
2023-10-09 17:59:50 +08:00
if ( this = = null ) return null ;
2023-11-09 21:06:18 +08:00
2023-09-24 15:59:43 +08:00
var mergeRoot = this . transform ;
var baseRoot = mergeTarget . Get ( this ) ;
2022-08-30 06:13:26 +08:00
2023-09-24 15:59:43 +08:00
if ( baseRoot = = null ) return null ;
2022-11-08 10:49:44 +08:00
2023-09-24 15:59:43 +08:00
List < ( Transform , Transform ) > mergeBones = new List < ( Transform , Transform ) > ( ) ;
2022-09-12 05:53:25 +08:00
2023-09-24 15:59:43 +08:00
ScanHierarchy ( mergeRoot , baseRoot . transform ) ;
2022-09-12 05:53:25 +08:00
2023-09-24 15:59:43 +08:00
return mergeBones ;
2022-10-03 09:16:58 +08:00
2022-09-12 05:53:25 +08:00
2023-09-24 15:59:43 +08:00
void ScanHierarchy ( Transform merge , Transform baseBone )
2022-09-12 05:53:25 +08:00
{
2023-09-24 15:59:43 +08:00
foreach ( Transform t in merge )
2022-08-30 06:13:26 +08:00
{
2024-02-27 18:02:04 +08:00
var subMerge = t . GetComponent < ModularAvatarMergeArmature > ( ) ;
if ( subMerge ! = null & & subMerge ! = this ) continue ;
2023-09-24 15:59:43 +08:00
var baseChild = FindCorrespondingBone ( t , baseBone ) ;
if ( baseChild ! = null )
2022-08-30 06:13:26 +08:00
{
2024-03-03 16:34:48 +08:00
mergeBones . Add ( ( baseChild , t ) ) ;
2023-09-24 15:59:43 +08:00
ScanHierarchy ( t , baseChild ) ;
2022-08-30 06:13:26 +08:00
}
}
}
}
2022-10-05 10:43:46 +08:00
2024-11-26 11:09:12 +08:00
class PSCandidate
{
public string prefix , suffix ;
public int matches ;
public PSCandidate CountMatches ( ModularAvatarMergeArmature merger )
{
var target = merger . mergeTarget . Get ( merger ) . transform ;
var source = merger . transform ;
var oldPrefix = merger . prefix ;
var oldSuffix = merger . suffix ;
try
{
merger . prefix = prefix ;
merger . suffix = suffix ;
matches = merger . GetBonesForLock ( ) . Count ;
return this ;
}
finally
{
merger . prefix = oldPrefix ;
merger . suffix = oldSuffix ;
}
}
/// <summary>
/// Counts the number of children which take the form prefix // heuristic bone name // suffix
/// </summary>
/// <returns></returns>
public PSCandidate CountHeuristicMatches ( Transform root )
{
int count = 1 ;
Walk ( root ) ;
matches = count ;
return this ;
void Walk ( Transform t )
{
foreach ( Transform child in t )
{
if ( child . name . StartsWith ( prefix ) & & child . name . EndsWith ( suffix ) )
{
var boneName = child . name . Substring ( prefix . Length , child . name . Length - prefix . Length - suffix . Length ) ;
boneName = NormalizeBoneName ( boneName ) ;
if ( AllBoneNames . Contains ( boneName ) )
{
count + + ;
Walk ( child ) ;
}
}
}
}
}
}
2022-10-05 10:43:46 +08:00
public void InferPrefixSuffix ( )
{
// We only infer if targeting the armature (below the Hips bone)
2023-10-15 17:44:53 +08:00
var rootAnimator = RuntimeUtil . FindAvatarTransformInParents ( transform ) ? . GetComponent < Animator > ( ) ;
2023-12-12 18:49:32 +08:00
if ( rootAnimator = = null | | ! rootAnimator . isHuman ) return ;
2022-10-05 10:43:46 +08:00
var hips = rootAnimator . GetBoneTransform ( HumanBodyBones . Hips ) ;
if ( hips = = null | | hips . transform . parent ! = mergeTargetObject . transform ) return ;
// We also require that the attached object has exactly one child (presumably the hips)
if ( transform . childCount ! = 1 ) return ;
2024-11-26 11:09:12 +08:00
List < PSCandidate > candidates = new ( ) ;
// always consider the current configuration
candidates . Add ( new PSCandidate ( ) { prefix = prefix , suffix = suffix } . CountMatches ( this ) ) ;
2022-10-05 10:43:46 +08:00
// Infer the prefix and suffix by comparing the names of the mergeTargetObject's hips with the child of the
// GameObject we're attached to.
var baseName = hips . name ;
2024-11-26 11:09:12 +08:00
var mergeHips = transform . GetChild ( 0 ) ;
var mergeName = mergeHips . name ;
// Classic substring match
{
var prefixLength = mergeName . IndexOf ( baseName , StringComparison . InvariantCulture ) ;
if ( prefixLength > = 0 )
{
var suffixLength = mergeName . Length - prefixLength - baseName . Length ;
candidates . Add ( new PSCandidate ( )
{
prefix = mergeName . Substring ( 0 , prefixLength ) ,
suffix = mergeName . Substring ( mergeName . Length - suffixLength )
} . CountMatches ( this ) ) ;
}
}
2022-10-05 10:43:46 +08:00
2024-11-26 11:09:12 +08:00
// Heuristic match - try to see if we get a better prefix/suffix pattern if we allow for fuzzy-matching of
// bone names. Since our goal is to minimize unnecessary renaming (and potentially failing matches), we do
// this only if the number of heuristic matches is more than twice the number of matches from the static
// pattern above, as using this will force most bones to be renamed.
foreach ( var hipNameCandidate in
boneNamePatterns [ ( int ) HumanBodyBones . Hips ] . OrderByDescending ( p = > p . Length ) )
2024-11-03 06:17:24 +08:00
{
var prefixLength = mergeName . IndexOf ( hipNameCandidate , StringComparison . InvariantCultureIgnoreCase ) ;
if ( prefixLength < 0 ) continue ;
var suffixLength = mergeName . Length - prefixLength - hipNameCandidate . Length ;
2024-11-26 11:09:12 +08:00
var prefix = mergeName . Substring ( 0 , prefixLength ) ;
var suffix = mergeName . Substring ( mergeName . Length - suffixLength ) ;
2024-11-03 06:17:24 +08:00
2024-11-26 11:09:12 +08:00
var candidate = new PSCandidate
{
prefix = prefix ,
suffix = suffix
} . CountHeuristicMatches ( mergeHips ) ;
candidate . matches = ( candidate . matches + 1 ) / 2 ;
2022-10-05 10:43:46 +08:00
2024-11-26 11:09:12 +08:00
candidates . Add ( candidate ) ;
break ;
}
// Select which candidate to use
var selected = candidates . OrderByDescending ( c = > c . matches ) . FirstOrDefault ( ) ;
if ( selected ! = null & & selected . matches > 0 )
{
prefix = selected . prefix ;
suffix = selected . suffix ;
2024-11-03 06:17:24 +08:00
}
2022-10-05 10:43:46 +08:00
2024-02-27 18:02:04 +08:00
if ( prefix = = "J_Bip_C_" )
{
// VRM workaround
prefix = "J_Bip_" ;
}
2022-10-05 10:43:46 +08:00
if ( ! string . IsNullOrEmpty ( prefix ) | | ! string . IsNullOrEmpty ( suffix ) )
{
RuntimeUtil . MarkDirty ( this ) ;
}
}
2024-09-02 08:29:58 +08:00
public IEnumerable < AvatarObjectReference > GetObjectReferences ( )
{
if ( mergeTarget ! = null ) yield return mergeTarget ;
}
2022-08-28 04:38:52 +08:00
}
2024-11-03 06:17:24 +08:00
}