yum/3ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum/3ner
1fc2239
master
1using System . Collections . Generic ; 2using UnityEditor ; 3using UnityEngine ; 4 5namespace Yum . Fold 6{ 7public static class FoldKeyframes 8{ 9public struct Field 10{ 11public string name ; 12public float value ; 13public bool discrete ; 14public Field ( string name , float value , bool discrete = false ) 15{ this . name = name ; this . value = value ; this . discrete = discrete ; } 16} 17 18// Unity 2022.3 discrete integer curves store integer bits in the float key value. 19// Numeric conversion would play opcode 12 as 1094713344. 20[ System . Runtime . InteropServices . StructLayout ( System . Runtime . InteropServices . LayoutKind . Explicit )] 21struct DiscreteValue 22{ 23[ System . Runtime . InteropServices . FieldOffset ( 0 )] public int integer ; 24[ System . Runtime . InteropServices . FieldOffset ( 0 )] public float floating ; 25} 26static float CurveValue ( Field field ) => field . name == "opcode" 27? new DiscreteValue { integer = Mathf . RoundToInt ( field . value ) }. floating : field . value ; 28 29public static List < Field > Fields ( FoldOperator op ) 30{ 31var fields = new List < Field > { 32new Field ( "enabled" , op . enabled ? 1 : 0 , true ), new Field ( "opcode" , op . opcode , true ), 33new Field ( "float0" , op . float0 ), new Field ( "float1" , op . float1 ), 34new Field ( "float2" , op . float2 ), new Field ( "float3" , op . float3 ) 35}; 36var vectors = new [] { op . vector0 , op . vector1 , op . vector2 , op . vector3 }; 37for ( int i = 0 ; i < 4 ; i ++ ) 38for ( int c = 0 ; c < 4 ; c ++ ) fields . Add ( new Field ( "vector" + i + "." + "xyzw" [ c ], vectors [ i ][ c ])); 39return fields ; 40} 41 42public static string Prefix ( int material , int slot ) => "slot" + slot + "_" ; 43static EditorCurveBinding Binding ( string path , string prefix , Field field ) => field . name == "opcode" 44? EditorCurveBinding . DiscreteCurve ( path , typeof ( FoldMaterialAnimator ), prefix + field . name ) 45: EditorCurveBinding . FloatCurve ( path , typeof ( FoldMaterialAnimator ), prefix + field . name ); 46 47// Compare actual preceding keys, not interpolated values or transient window state. 48// Also repair partial operator keys made by the Animation window's record mode. 49public static bool Changed ( AnimationClip clip , string path , string prefix , List < Field > fields , float time ) 50{ 51float latest = float . NegativeInfinity ; 52var times = new List < float > (); 53foreach ( var field in fields ) 54{ 55var curve = AnimationUtility . GetEditorCurve ( clip , Binding ( path , prefix , field )); 56bool found = false ; 57float keyTime = float . NegativeInfinity ; 58float value = 0 ; 59if ( curve != null ) 60foreach ( var key in curve . keys ) 61if ( key . time <= time + .00001f ) { found = true ; keyTime = key . time ; value = key . value ; } 62if ( ! found || ! value . Equals ( CurveValue ( field ))) return true ; 63times . Add ( keyTime ); latest = Mathf . Max ( latest , keyTime ); 64} 65foreach ( float keyTime in times ) if ( Mathf . Abs ( keyTime - latest ) > .00001f ) return true ; 66return false ; 67} 68 69// Clear the current frame across every track in the clip, including object references. 70public static int Clear ( AnimationClip clip , float time ) 71{ 72int frame = Mathf . RoundToInt ( Mathf . Max ( 0 , time ) * clip . frameRate ); 73var bindings = new List < EditorCurveBinding > (); 74var curves = new List < AnimationCurve > (); 75var objectBindings = new List < EditorCurveBinding > (); 76var objectCurves = new List < ObjectReferenceKeyframe [] > (); 77int count = 0 ; 78foreach ( var binding in AnimationUtility . GetCurveBindings ( clip )) 79{ 80var curve = AnimationUtility . GetEditorCurve ( clip , binding ); 81int before = curve . length ; 82for ( int i = curve . length - 1 ; i >= 0 ; i -- ) 83if ( Mathf . RoundToInt ( curve [ i ]. time * clip . frameRate ) == frame ) curve . RemoveKey ( i ); 84if ( curve . length == before ) continue ; 85count += before - curve . length ; 86bindings . Add ( binding ); 87curves . Add ( curve . length == 0 ? null : curve ); 88} 89foreach ( var binding in AnimationUtility . GetObjectReferenceCurveBindings ( clip )) 90{ 91var keys = AnimationUtility . GetObjectReferenceCurve ( clip , binding ); 92var remaining = new List < ObjectReferenceKeyframe > (); 93foreach ( var key in keys ) 94if ( Mathf . RoundToInt ( key . time * clip . frameRate ) != frame ) remaining . Add ( key ); 95if ( remaining . Count == keys . Length ) continue ; 96count += keys . Length - remaining . Count ; 97objectBindings . Add ( binding ); 98objectCurves . Add ( remaining . Count == 0 ? null : remaining . ToArray ()); 99} 100if ( count == 0 ) return 0 ; 101Undo . RegisterCompleteObjectUndo ( clip , "Clear keyframe" ); 102AnimationUtility . SetEditorCurves ( clip , bindings . ToArray (), curves . ToArray ()); 103for ( int i = 0 ; i < objectBindings . Count ; i ++ ) 104AnimationUtility . SetObjectReferenceCurve ( clip , objectBindings [ i ], objectCurves [ i ]); 105EditorUtility . SetDirty ( clip ); 106return count ; 107} 108 109// Convert only complete linear numeric spans. Tangents belong to a side of 110// a key: a handoff can keep its stepped incoming side and ease out normally. 111public static int EaseLinearSpans ( AnimationClip clip ) 112{ 113var numeric = new HashSet < string > (); 114foreach ( var field in Fields ( new FoldOperator ())) 115if ( ! field . discrete ) 116for ( int slot = 0 ; slot < 16 ; slot ++ ) numeric . Add ( Prefix ( 0 , slot ) + field . name ); 117var bindings = new List < EditorCurveBinding > (); 118var curves = new List < AnimationCurve > (); 119int count = 0 ; 120foreach ( var binding in AnimationUtility . GetCurveBindings ( clip )) 121{ 122if ( binding . type != typeof ( FoldMaterialAnimator ) || binding . isDiscreteCurve || 123! numeric . Contains ( binding . propertyName )) continue ; 124var curve = AnimationUtility . GetEditorCurve ( clip , binding ); 125bool changed = false ; 126for ( int i = 0 ; i + 1 < curve . length ; i ++ ) 127{ 128if ( curve [ i ]. value . Equals ( curve [ i + 1 ]. value ) || 129AnimationUtility . GetKeyRightTangentMode ( curve , i ) != AnimationUtility . TangentMode . Linear || 130AnimationUtility . GetKeyLeftTangentMode ( curve , i + 1 ) != AnimationUtility . TangentMode . Linear || 131float . IsInfinity ( curve [ i ]. outTangent ) || float . IsInfinity ( curve [ i + 1 ]. inTangent )) continue ; 132FlattenSide ( curve , i , false ); 133FlattenSide ( curve , i + 1 , true ); 134changed = true ; 135count ++ ; 136} 137if ( changed ) { bindings . Add ( binding ); curves . Add ( curve ); } 138} 139if ( count > 0 ) 140{ 141Undo . RegisterCompleteObjectUndo ( clip , "Ease Fold animation" ); 142AnimationUtility . SetEditorCurves ( clip , bindings . ToArray (), curves . ToArray ()); 143EditorUtility . SetDirty ( clip ); 144} 145return count ; 146} 147 148static void FlattenSide ( AnimationCurve curve , int index , bool incoming ) 149{ 150AnimationUtility . SetKeyBroken ( curve , index , true ); 151if ( incoming ) AnimationUtility . SetKeyLeftTangentMode ( curve , index , AnimationUtility . TangentMode . Free ); 152else AnimationUtility . SetKeyRightTangentMode ( curve , index , AnimationUtility . TangentMode . Free ); 153var key = curve [ index ]; 154if ( incoming ) { key . inTangent = 0 ; key . weightedMode &= ~ WeightedMode . In ; } 155else { key . outTangent = 0 ; key . weightedMode &= ~ WeightedMode . Out ; } 156curve . MoveKey ( index , key ); 157} 158 159static float PreviousAppearanceTime ( AnimationClip clip , float time ) 160{ 161float previous = float . NegativeInfinity ; 162foreach ( var binding in AnimationUtility . GetCurveBindings ( clip )) 163{ 164if ( binding . type != typeof ( FoldMaterialAnimator ) || ! binding . propertyName . StartsWith ( "slot" )) continue ; 165foreach ( var key in AnimationUtility . GetEditorCurve ( clip , binding ). keys ) 166if ( key . time < time - .00001f ) previous = Mathf . Max ( previous , key . time ); 167} 168return previous ; 169} 170 171static void Key ( AnimationCurve curve , float time , float value , bool discrete ) 172{ 173var left = discrete ? AnimationUtility . TangentMode . Constant : AnimationUtility . TangentMode . Linear ; 174var right = left ; 175for ( int i = curve . length - 1 ; i >= 0 ; i -- ) 176if ( Mathf . Abs ( curve [ i ]. time - time ) < .00001f ) 177{ 178// Re-keying a pose must not erase an existing stepped reorder boundary. 179if ( AnimationUtility . GetKeyLeftTangentMode ( curve , i ) == AnimationUtility . TangentMode . Constant ) 180left = AnimationUtility . TangentMode . Constant ; 181if ( AnimationUtility . GetKeyRightTangentMode ( curve , i ) == AnimationUtility . TangentMode . Constant ) 182right = AnimationUtility . TangentMode . Constant ; 183curve . RemoveKey ( i ); 184} 185int index = curve . AddKey ( new Keyframe ( time , value )); 186AnimationUtility . SetKeyBroken ( curve , index , left != right ); 187AnimationUtility . SetKeyLeftTangentMode ( curve , index , left ); 188AnimationUtility . SetKeyRightTangentMode ( curve , index , right ); 189// Refresh neighboring cached slopes after insertion while preserving their tangent modes. 190if ( index > 0 ) AnimationUtility . SetKeyRightTangentMode ( curve , index - 1 , AnimationUtility . GetKeyRightTangentMode ( curve , index - 1 )); 191if ( index + 1 < curve . length ) AnimationUtility . SetKeyLeftTangentMode ( curve , index + 1 , AnimationUtility . GetKeyLeftTangentMode ( curve , index + 1 )); 192} 193 194static void HoldPreviousState ( AnimationCurve curve , float time , bool discrete ) 195{ 196float value = 0 ; 197foreach ( var key in curve . keys ) 198{ 199// Keep existing keys and their tangents. Never sample interpolation towards a future pose. 200if ( Mathf . Abs ( key . time - time ) < .00001f ) return ; 201if ( key . time > time ) break ; 202value = key . value ; 203} 204Key ( curve , time , value , discrete ); 205} 206 207static float LastValue ( AnimationCurve curve , float time ) 208{ 209float value = 0 ; 210if ( curve != null ) 211foreach ( var key in curve . keys ) 212{ 213if ( key . time > time + .00001f ) break ; 214value = key . value ; 215} 216return value ; 217} 218 219// Snapshot all sources before writing destination curves. A swap must read the 220// original state on both sides, never the state just written into the first slot. 221static float [][] PreviousStates ( AnimationClip clip , string path , float time ) 222{ 223var fields = Fields ( new FoldOperator ()); 224var states = new float [ 16 ][]; 225for ( int slot = 0 ; slot < states . Length ; slot ++ ) 226{ 227states [ slot ] = new float [ fields . Count ]; 228for ( int f = 0 ; f < fields . Count ; f ++ ) 229states [ slot ][ f ] = LastValue ( AnimationUtility . GetEditorCurve ( clip , 230Binding ( path , Prefix ( 0 , slot ), fields [ f ])), time ); 231} 232return states ; 233} 234 235static bool SameState ( float [] previous , List < Field > current ) 236{ 237for ( int f = 0 ; f < current . Count ; f ++ ) 238if ( ! previous [ f ]. Equals ( CurveValue ( current [ f ]))) return false ; 239return true ; 240} 241 242static int [] ReorderSources ( FoldMaterialAnimator material , float [][] previous , out bool [] affected ) 243{ 244var sources = new int [ 16 ]; 245var used = new bool [ 16 ]; 246var oldOpcodes = new int [ 16 ]; 247var current = new List < Field > [ 16 ]; 248affected = new bool [ 16 ]; 249for ( int slot = 0 ; slot < 16 ; slot ++ ) 250{ 251sources [ slot ] = - 1 ; 252oldOpcodes [ slot ] = new DiscreteValue { floating = previous [ slot ][ 1 ] }. integer ; 253current [ slot ] = Fields ( material . slots [ slot ]); 254} 255 256// Preserve the longest unchanged sequence first. A newly inserted operator 257// can equal an existing one farther down the stack; greedy matching would 258// steal that source and reset the real survivor to its jumpback defaults. 259var matches = new bool [ 16 , 16 ]; 260var lengths = new int [ 17 , 17 ]; 261for ( int source = 15 ; source >= 0 ; source -- ) 262for ( int destination = 15 ; destination >= 0 ; destination -- ) 263{ 264matches [ source , destination ] = oldOpcodes [ source ] != 0 && 265SameState ( previous [ source ], current [ destination ]); 266lengths [ source , destination ] = matches [ source , destination ] 267? 1 + lengths [ source + 1 , destination + 1 ] 268: System . Math . Max ( lengths [ source + 1 , destination ], lengths [ source , destination + 1 ]); 269} 270for ( int source = 0 , destination = 0 ; source < 16 && destination < 16 ;) 271{ 272if ( matches [ source , destination ]) 273{ 274sources [ destination ] = source ; 275used [ source ] = true ; 276source ++ ; destination ++ ; 277} 278else if ( lengths [ source + 1 , destination ] > lengths [ source , destination + 1 ]) source ++ ; 279else destination ++ ; 280} 281// Match the remaining exact states to support arbitrary reorders too. 282for ( int destination = 0 ; destination < 16 ; destination ++ ) 283{ 284if ( sources [ destination ] >= 0 || current [ destination ][ 1 ]. value == 0 ) continue ; 285for ( int source = 0 ; source < 16 ; source ++ ) 286if ( ! used [ source ] && SameState ( previous [ source ], current [ destination ])) 287{ sources [ destination ] = source ; used [ source ] = true ; break ; } 288} 289 290// Remaining same-slot operators may have ordinary parameter edits. 291for ( int slot = 0 ; slot < 16 ; slot ++ ) 292if ( sources [ slot ] < 0 && ! used [ slot ] && oldOpcodes [ slot ] == current [ slot ][ 1 ]. value ) 293{ sources [ slot ] = slot ; used [ slot ] = true ; } 294 295// A moved operator may also have been edited. Match by opcode only when 296// both its remaining source and destination are unambiguous. 297for ( int destination = 0 ; destination < 16 ; destination ++ ) 298{ 299int opcode = ( int ) current [ destination ][ 1 ]. value ; 300if ( opcode == 0 || sources [ destination ] >= 0 ) continue ; 301int source = - 1 , candidates = 0 , destinations = 0 ; 302for ( int slot = 0 ; slot < 16 ; slot ++ ) 303{ 304if ( ! used [ slot ] && oldOpcodes [ slot ] == opcode ) { source = slot ; candidates ++ ; } 305if ( sources [ slot ] < 0 && current [ slot ][ 1 ]. value == opcode ) destinations ++ ; 306} 307if ( candidates == 1 && destinations == 1 ) 308{ sources [ destination ] = source ; used [ source ] = true ; } 309} 310for ( int destination = 0 ; destination < 16 ; destination ++ ) 311{ 312int source = sources [ destination ]; 313if ( source >= 0 && source != destination ) 314{ affected [ destination ] = true ; affected [ source ] = true ; } 315// Includes a same-opcode replacement whose old occupant moved elsewhere. 316if ( oldOpcodes [ destination ] != 0 && source != destination ) affected [ destination ] = true ; 317} 318return sources ; 319} 320 321static void StepInto ( AnimationCurve curve , float previousTime , float transitionTime , bool discrete ) 322{ 323for ( int i = 0 ; i < curve . length ; i ++ ) 324{ 325if ( Mathf . Abs ( curve [ i ]. time - previousTime ) < .00001f ) 326{ 327// Only change the outgoing side; the preceding animation keeps its tangent. 328AnimationUtility . SetKeyBroken ( curve , i , true ); 329AnimationUtility . SetKeyRightTangentMode ( curve , i , AnimationUtility . TangentMode . Constant ); 330} 331if ( Mathf . Abs ( curve [ i ]. time - transitionTime ) < .00001f ) 332{ 333AnimationUtility . SetKeyBroken ( curve , i , true ); 334AnimationUtility . SetKeyLeftTangentMode ( curve , i , AnimationUtility . TangentMode . Constant ); 335AnimationUtility . SetKeyRightTangentMode ( curve , i , discrete 336? AnimationUtility . TangentMode . Constant : AnimationUtility . TangentMode . Linear ); 337} 338} 339} 340 341static void InterpolateBetween ( AnimationCurve curve , float a , float b ) 342{ 343float start = Mathf . Min ( a , b ), end = Mathf . Max ( a , b ); 344if ( end - start < .00001f ) return ; 345for ( int i = 0 ; i < curve . length ; i ++ ) 346{ 347if ( Mathf . Abs ( curve [ i ]. time - start ) < .00001f ) 348{ 349AnimationUtility . SetKeyBroken ( curve , i , true ); 350AnimationUtility . SetKeyRightTangentMode ( curve , i , AnimationUtility . TangentMode . Linear ); 351} 352if ( Mathf . Abs ( curve [ i ]. time - end ) < .00001f ) 353{ 354AnimationUtility . SetKeyBroken ( curve , i , true ); 355AnimationUtility . SetKeyLeftTangentMode ( curve , i , AnimationUtility . TangentMode . Linear ); 356} 357} 358} 359 360static bool HasNonDefaultInputs ( FoldOperator op , List < Field > fields , List < Field > defaults ) 361{ 362// Identity scale and zero translation are neutral regardless of t. Ignore 363// unused vector components/fields for these identity configurations. 364if ( op . opcode == 11 && (( Vector3 ) op . vector0 ). Equals ( Vector3 . one )) return false ; 365if ( op . opcode == 12 && (( Vector3 ) op . vector0 ). Equals ( Vector3 . zero )) return false ; 366for ( int f = 0 ; f < fields . Count ; f ++ ) 367if ( fields [ f ]. name != "opcode" && fields [ f ]. name != "enabled" && 368! fields [ f ]. value . Equals ( defaults [ f ]. value )) return true ; 369return false ; 370} 371 372// Recover an existing generated boundary from the saved curves, so this also 373// works after reopening the editor and after Undo/Redo. Require complete keys 374// at both ends; partial keys must not be mistaken for a generated boundary. 375static bool ExistingJumpback ( AnimationClip clip , string path , int slot , float time , 376out float anchor , out bool handoff ) 377{ 378anchor = float . NaN ; 379handoff = false ; 380var fields = Fields ( new FoldOperator ()); 381var end = new float [ fields . Count ]; 382var start = new float [ fields . Count ]; 383int beforeOpcode = 0 ; 384for ( int f = 0 ; f < fields . Count ; f ++ ) 385{ 386var curve = AnimationUtility . GetEditorCurve ( clip , Binding ( path , Prefix ( 0 , slot ), fields [ f ])); 387if ( curve == null ) return false ; 388int index = - 1 ; 389for ( int k = 0 ; k < curve . length ; k ++ ) 390if ( Mathf . Abs ( curve [ k ]. time - time ) < .00001f ) { index = k ; break ; } 391if ( index < 1 ) return false ; 392float candidate = curve [ index - 1 ]. time ; 393if ( f == 0 ) anchor = candidate ; 394else if ( Mathf . Abs ( candidate - anchor ) > .00001f ) return false ; 395end [ f ] = curve [ index ]. value ; 396start [ f ] = curve [ index - 1 ]. value ; 397if ( f == 1 && index > 1 ) 398beforeOpcode = new DiscreteValue { floating = curve [ index - 2 ]. value }. integer ; 399if ( ! fields [ f ]. discrete && index > 1 && 400AnimationUtility . GetKeyLeftTangentMode ( curve , index - 1 ) == AnimationUtility . TangentMode . Constant && 401AnimationUtility . GetKeyRightTangentMode ( curve , index - 2 ) == AnimationUtility . TangentMode . Constant ) 402handoff = true ; 403} 404if ( handoff ) return true ; 405int opcode = new DiscreteValue { floating = end [ 1 ] }. integer ; 406if ( opcode == 0 || beforeOpcode != 0 || ! start [ 1 ]. Equals ( end [ 1 ])) return false ; 407var saved = new FoldOperator { 408enabled = end [ 0 ] > .5f , opcode = opcode , 409float0 = end [ 2 ], float1 = end [ 3 ], float2 = end [ 4 ], float3 = end [ 5 ], 410vector0 = new Vector4 ( end [ 6 ], end [ 7 ], end [ 8 ], end [ 9 ]), 411vector1 = new Vector4 ( end [ 10 ], end [ 11 ], end [ 12 ], end [ 13 ]), 412vector2 = new Vector4 ( end [ 14 ], end [ 15 ], end [ 16 ], end [ 17 ]), 413vector3 = new Vector4 ( end [ 18 ], end [ 19 ], end [ 20 ], end [ 21 ]) 414}; 415// Ordinary additions have a neutral key, without the stepped handoff. 416return SameState ( start , Fields ( saved . JumpbackDefaults ())); 417} 418 419public static int Record ( AnimationClip clip , Transform root , FoldStack stack , float time , int advanceFrames = 0 , 420System . Func < FoldMaterialAnimator , int , bool > disableJumpBackKeyframing = null ) 421{ 422time = Mathf . Round ( Mathf . Max ( 0 , time ) * clip . frameRate ) / clip . frameRate ; 423float appearanceTime = PreviousAppearanceTime ( clip , time ); 424float requestedTransitionTime = time ; 425float previousTime = appearanceTime ; 426if ( ! float . IsNegativeInfinity ( previousTime )) 427{ 428long frame = Mathf . RoundToInt ( time * clip . frameRate ); 429long previousFrame = Mathf . RoundToInt ( previousTime * clip . frameRate ); 430// Advance can be negative for backwards authoring. Playback transitions still 431// belong after the preceding pose, never before it or beyond the current pose. 432long requested = advanceFrames == 0 ? previousFrame + 1 433: frame - System . Math . Abs (( long ) advanceFrames ) + 1 ; 434requestedTransitionTime = System . Math . Min ( frame , System . Math . Max ( previousFrame + 1 , requested )) / clip . frameRate ; 435} 436 437var bindings = new List < EditorCurveBinding > (); 438var curves = new List < AnimationCurve > (); 439int count = 0 ; 440for ( int m = 0 ; m < stack . materials . Length ; m ++ ) 441{ 442var material = stack . materials [ m ]; 443if ( ! material || ! material . material ) continue ; 444string path = AnimationUtility . CalculateTransformPath ( material . transform , root ); 445previousTime = appearanceTime ; 446float transitionTime = requestedTransitionTime ; 447var reuseDefault = new bool [ 16 ]; 448var anchors = new float [ 16 ]; 449for ( int slot = 0 ; slot < 16 ; slot ++ ) 450{ 451if ( disableJumpBackKeyframing ? . Invoke ( material , slot ) ?? false ) continue ; 452if ( ! ExistingJumpback ( clip , path , slot , time , out anchors [ slot ], out bool handoff )) continue ; 453reuseDefault [ slot ] = ! handoff ; 454if ( handoff && Mathf . Abs ( anchors [ slot ] - appearanceTime ) < .00001f ) 455{ 456transitionTime = anchors [ slot ]; 457previousTime = PreviousAppearanceTime ( clip , anchors [ slot ]); 458} 459} 460var previous = PreviousStates ( clip , path , previousTime ); 461var sources = ReorderSources ( material , previous , out var affected ); 462for ( int s = 0 ; s < material . slots . Length ; s ++ ) 463{ 464var op = material . slots [ s ]; 465float slotTransitionTime = reuseDefault [ s ] ? anchors [ s ] : transitionTime ; 466bool cursorOnly = disableJumpBackKeyframing ? . Invoke ( material , s ) ?? false ; 467var fields = Fields ( op ); 468int previousOpcode = new DiscreteValue { floating = previous [ s ][ 1 ] }. integer ; 469bool added = op . opcode != 0 && ( reuseDefault [ s ] || ( sources [ s ] < 0 && ( previousOpcode == 0 || affected [ s ]))); 470var defaults = added ? Fields ( op . JumpbackDefaults ()) : null ; 471bool needsDefaultKey = added && HasNonDefaultInputs ( op , fields , defaults ); 472float defaultTime = Mathf . Max ( 0 , time - advanceFrames / clip . frameRate ); 473// A reordered insertion cannot occupy its destination before the old 474// operator has moved out. Its default pose belongs at the handoff instead. 475if ( added && affected [ s ]) defaultTime = slotTransitionTime ; 476if ( reuseDefault [ s ]) defaultTime = anchors [ s ]; 477string prefix = Prefix ( m , s ); 478var opcodeCurve = added ? AnimationUtility . GetEditorCurve ( clip , Binding ( path , prefix , fields [ 1 ])) : null ; 479if ( needsDefaultKey && ! reuseDefault [ s ] && ! affected [ s ] && defaultTime < time && previousOpcode == 0 && opcodeCurve != null ) 480{ 481// Do not replace a different operator's earlier use of this material slot. 482bool occupied = new DiscreteValue { floating = LastValue ( opcodeCurve , defaultTime ) }. integer != 0 ; 483foreach ( var key in opcodeCurve . keys ) 484if ( key . time > defaultTime && key . time < time && new DiscreteValue { floating = key . value }. integer != 0 ) occupied = true ; 485if ( occupied && ! float . IsNegativeInfinity ( previousTime )) defaultTime = Mathf . Max ( defaultTime , previousTime ); 486} 487if (( cursorOnly || ! affected [ s ]) && ! Changed ( clip , path , prefix , fields , time )) continue ; 488count ++ ; 489for ( int f = 0 ; f < fields . Count ; f ++ ) 490{ 491var field = fields [ f ]; 492var binding = Binding ( path , prefix , field ); 493var curve = AnimationUtility . GetEditorCurve ( clip , binding ) ?? new AnimationCurve (); 494if ( cursorOnly ) 495{ 496Key ( curve , time , CurveValue ( field ), field . discrete ); 497bindings . Add ( binding ); curves . Add ( curve ); 498continue ; 499} 500if ( added && ( needsDefaultKey || affected [ s ] || reuseDefault [ s ])) 501{ 502// Reorders still need their structural handoff even for a default 503// occupant. Ordinary additions at their opcode defaults only get the current key. 504// Initial empty-stack snapshots inside the new span would interrupt 505// the default-to-user interpolation. Remove only keys from times when 506// this slot had no operator, and only inside this operator's new span. 507float start = Mathf . Min ( defaultTime , time ), end = Mathf . Max ( defaultTime , time ); 508for ( int i = curve . length - 1 ; i >= 0 ; i -- ) 509if ( curve [ i ]. time > start + .00001f && curve [ i ]. time < end - .00001f && 510new DiscreteValue { floating = LastValue ( opcodeCurve , curve [ i ]. time ) }. integer == 0 ) curve . RemoveKey ( i ); 511if ( ! float . IsNegativeInfinity ( previousTime ) && 512( affected [ s ] || Mathf . Abs ( defaultTime - time ) < .00001f )) 513HoldPreviousState ( curve , previousTime , field . discrete ); 514if ( Mathf . Abs ( defaultTime - time ) > .00001f ) 515Key ( curve , defaultTime , CurveValue ( defaults [ f ]), field . discrete ); 516} 517else if ( ! added ) 518{ 519if ( ! float . IsNegativeInfinity ( previousTime )) 520HoldPreviousState ( curve , previousTime , field . discrete ); 521if ( affected [ s ] && slotTransitionTime < time - .00001f ) 522{ 523float previousValue = sources [ s ] >= 0 ? previous [ sources [ s ]][ f ] : 0 ; 524if ( field . name == "opcode" ) previousValue = CurveValue ( field ); 525Key ( curve , slotTransitionTime , previousValue , field . discrete ); 526} 527} 528Key ( curve , time , CurveValue ( field ), field . discrete ); 529if ( affected [ s ]) StepInto ( curve , previousTime , slotTransitionTime , field . discrete ); 530if ( ! field . discrete ) 531{ 532// Restore BOTH ends of the interpolated span. Key() preserves 533// Constant tangents, which may be stale after replacing/clearing 534// reorder keys. Preserve only the step into the reordered layout. 535if ( affected [ s ]) InterpolateBetween ( curve , slotTransitionTime , time ); 536else if ( needsDefaultKey || reuseDefault [ s ]) InterpolateBetween ( curve , defaultTime , time ); 537else if ( ! added && previousOpcode == op . opcode && ! float . IsNegativeInfinity ( previousTime )) 538InterpolateBetween ( curve , previousTime , time ); 539} 540bindings . Add ( binding ); curves . Add ( curve ); 541} 542} 543} 544if ( count > 0 ) 545{ 546Undo . RegisterCompleteObjectUndo ( clip , "Keyframe Fold appearance" ); 547AnimationUtility . SetEditorCurves ( clip , bindings . ToArray (), curves . ToArray ()); 548EditorUtility . SetDirty ( clip ); 549} 550return count ; 551} 552} 553}