@@ -478,7 +478,9 @@ private static void BuildInputsLayer(ControllerBuilder builder, ClipStore clips,
478478 /// </summary>
479479 private static BlendTree InputsTree ( ControllerBuilder builder , ClipStore clips , FTExtrasGenerationSettings settings , CustomAnimationPlan customs , bool includeJaw )
480480 {
481- var outputs = new [ ] { ParamGazeLeftX , ParamGazeRightX , ParamGazeY , ParamMood , ParamJaw } ;
481+ var outputs = new [ ] { ParamGazeLeftX , ParamGazeRightX , ParamGazeY , ParamMood , ParamJaw }
482+ . Concat ( customs . Animations . Where ( a => a . HasComputedDriver ) . Select ( a => a . Driver ) )
483+ . ToArray ( ) ;
482484 AnimationClip zero = clips . Parameters ( "Inputs - Zero" , outputs . Select ( p => ( p , 0f ) ) . ToArray ( ) ) ;
483485
484486 BlendTree root = builder . NewTree ( includeJaw ? "Inputs (Jaw On)" : "Inputs (Jaw Off)" , BlendTreeType . Direct , null ) ;
@@ -497,6 +499,13 @@ private static BlendTree InputsTree(ControllerBuilder builder, ClipStore clips,
497499 children . Add ( Direct ( RangeTree ( builder , clips , settings . jawX , settings . jawFullAt , ParamJaw , 1f ) , settings . lipTrackingActive ) ) ;
498500 }
499501
502+ // Followed averages: each parameter adds half of its value.
503+ foreach ( CustomAnimationSpec spec in customs . Animations . Where ( a => a . HasComputedDriver ) )
504+ {
505+ children . Add ( Direct ( HalfTree ( builder , clips , spec . Animation . followParameter , spec . Driver , $ "{ spec . SafeName } A") , ParamOne ) ) ;
506+ children . Add ( Direct ( HalfTree ( builder , clips , spec . Animation . followParameterB , spec . Driver , $ "{ spec . SafeName } B") , ParamOne ) ) ;
507+ }
508+
500509 // Scrubbed custom animations: map the parameter's from..to range onto a 0..1 playback position.
501510 foreach ( CustomAnimationSpec spec in customs . Animations . Where ( a => a . ScrubParameter != null ) )
502511 {
@@ -508,15 +517,31 @@ private static BlendTree InputsTree(ControllerBuilder builder, ClipStore clips,
508517 }
509518
510519 /// <summary>
511- /// 1D tree mapping the Follow parameter's from..to range to a 0..1 playback position (clamped outside).
520+ /// 1D tree writing half of the input into the output, linear over the parameter's range (at least
521+ /// -1..1); two of them in the Direct tree make the average.
522+ /// </summary>
523+ private static BlendTree HalfTree ( ControllerBuilder builder , ClipStore clips , string input , string output , string label )
524+ {
525+ FTExtrasParameterCatalog . Entry info = FTExtrasParameterCatalog . Describe ( input ) ;
526+ float low = Mathf . Min ( - 1f , info . Min ) ;
527+ float high = Mathf . Max ( 1f , info . Max ) ;
528+
529+ BlendTree tree = builder . NewTree ( $ "Custom - { label } half of { input } ", BlendTreeType . Simple1D , input ) ;
530+ tree . AddChild ( clips . Parameters ( $ "Custom - { label } - Low", ( output , low * 0.5f ) ) , low ) ;
531+ tree . AddChild ( clips . Parameters ( $ "Custom - { label } - High", ( output , high * 0.5f ) ) , high ) ;
532+ return tree ;
533+ }
534+
535+ /// <summary>
536+ /// 1D tree mapping the Follow value's from..to range to a 0..1 playback position (clamped outside).
512537 /// </summary>
513538 private static BlendTree PositionTree ( ControllerBuilder builder , ClipStore clips , CustomAnimationSpec spec )
514539 {
515540 FTExtrasCustomAnimation animation = spec . Animation ;
516541 AnimationClip start = clips . Parameters ( $ "Custom - { spec . SafeName } - Position 0", ( spec . ScrubParameter , 0f ) ) ;
517542 AnimationClip end = clips . Parameters ( $ "Custom - { spec . SafeName } - Position 1", ( spec . ScrubParameter , 1f ) ) ;
518543
519- BlendTree tree = builder . NewTree ( $ "{ animation . name } position from { animation . followParameter } ", BlendTreeType . Simple1D , animation . followParameter ) ;
544+ BlendTree tree = builder . NewTree ( $ "{ animation . name } position from { spec . Driver } ", BlendTreeType . Simple1D , spec . Driver ) ;
520545 AddSorted ( tree , ( animation . fromValue , start ) , ( animation . toValue , end ) ) ;
521546 return tree ;
522547 }
@@ -771,6 +796,10 @@ private class CustomAnimationSpec
771796 public List < string > Gates = new List < string > ( ) ;
772797 public string Toggle ;
773798 public string ScrubParameter ;
799+
800+ /// <summary>Follow: the parameter the layer reads; a computed average when following two.</summary>
801+ public string Driver ;
802+ public bool HasComputedDriver => Driver != null && Driver != Animation . followParameter ;
774803 }
775804
776805 /// <summary>
@@ -819,6 +848,11 @@ public CustomAnimationPlan(FTExtrasProfile profile, FTExtrasGenerationSettings s
819848 Toggles . Add ( new CustomToggle { Label = animation . name , Parameter = spec . Toggle , DefaultOn = animation . toggleDefaultOn } ) ;
820849 }
821850
851+ if ( animation . mode == FTExtrasCustomMode . Follow )
852+ {
853+ spec . Driver = animation . IsAveraged ? CustomParamPrefix + safeName + "/Value" : animation . followParameter ;
854+ }
855+
822856 if ( animation . mode == FTExtrasCustomMode . Follow && animation . followStyle == FTExtrasFollowStyle . Scrub )
823857 {
824858 spec . ScrubParameter = CustomParamPrefix + safeName + "/Position" ;
@@ -836,6 +870,7 @@ public void AddParameters(ControllerBuilder builder)
836870 foreach ( string gate in spec . Gates ) builder . AddFloat ( gate , 0f ) ;
837871 if ( spec . Toggle != null ) builder . AddBool ( spec . Toggle , spec . Animation . toggleDefaultOn ) ;
838872 if ( spec . ScrubParameter != null ) builder . AddFloat ( spec . ScrubParameter , 0f ) ;
873+ if ( spec . HasComputedDriver ) builder . AddFloat ( spec . Driver , 0f ) ;
839874 }
840875 }
841876
@@ -845,6 +880,7 @@ private static string DescribeProblem(FTExtrasCustomAnimation animation)
845880 if ( animation . mode == FTExtrasCustomMode . Follow )
846881 {
847882 if ( string . IsNullOrWhiteSpace ( animation . followParameter ) ) return "no parameter is set." ;
883+ if ( animation . followAverage && string . IsNullOrWhiteSpace ( animation . followParameterB ) ) return "the parameter to average with is not set." ;
848884 if ( Mathf . Approximately ( animation . fromValue , animation . toValue ) ) return "'From' and 'To' are the same value." ;
849885 }
850886 else
@@ -901,14 +937,14 @@ private static void BuildFollowLayer(ControllerBuilder builder, ClipStore clips,
901937 {
902938 case FTExtrasFollowStyle . FadeIn :
903939 {
904- BlendTree tree = builder . NewTree ( $ "{ animation . name } (fade in)", BlendTreeType . Simple1D , animation . followParameter ) ;
940+ BlendTree tree = builder . NewTree ( $ "{ animation . name } (fade in)", BlendTreeType . Simple1D , spec . Driver ) ;
905941 AddSorted ( tree , ( animation . fromValue , empty ) , ( animation . toValue , animation . clip ) ) ;
906942 active . motion = tree ;
907943 break ;
908944 }
909945 case FTExtrasFollowStyle . TwoSided :
910946 {
911- BlendTree tree = builder . NewTree ( $ "{ animation . name } (two-sided)", BlendTreeType . Simple1D , animation . followParameter ) ;
947+ BlendTree tree = builder . NewTree ( $ "{ animation . name } (two-sided)", BlendTreeType . Simple1D , spec . Driver ) ;
912948 float middle = ( animation . fromValue + animation . toValue ) * 0.5f ;
913949 AddSorted ( tree , ( animation . fromValue , animation . negativeClip != null ? ( Motion ) animation . negativeClip : empty ) , ( middle , empty ) , ( animation . toValue , animation . clip ) ) ;
914950 active . motion = tree ;
@@ -947,8 +983,8 @@ private static void BuildFollowLayer(ControllerBuilder builder, ClipStore clips,
947983 float span = Mathf . Abs ( animation . toValue - animation . fromValue ) ;
948984 float enter = animation . fromValue + direction * span * 0.02f ;
949985 float leave = animation . fromValue + direction * span * 0.01f ;
950- start . AddCondition ( direction > 0f ? AnimatorConditionMode . Greater : AnimatorConditionMode . Less , enter , animation . followParameter ) ;
951- Transition ( active , off , Blend ) . AddCondition ( direction > 0f ? AnimatorConditionMode . Less : AnimatorConditionMode . Greater , leave , animation . followParameter ) ;
986+ start . AddCondition ( direction > 0f ? AnimatorConditionMode . Greater : AnimatorConditionMode . Less , enter , spec . Driver ) ;
987+ Transition ( active , off , Blend ) . AddCondition ( direction > 0f ? AnimatorConditionMode . Less : AnimatorConditionMode . Greater , leave , spec . Driver ) ;
952988 }
953989
954990 AddStopTransitions ( sm , off , spec , Blend ) ;
0 commit comments