yum/3ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum/3ner
c09509e
master
1using System ; 2using System . Collections . Generic ; 3using System . Reflection ; 4using UnityEditor ; 5using UnityEngine ; 6 7public class FoldEditorWindow : EditorWindow 8{ 9[ SerializeField ] Material targetMaterial ; 10[ SerializeField ] Vector2 scrollPos ; 11[ SerializeReference ] List < DeformOperation > operations = new (); 12[ SerializeField ] List < int > expandedOps = new (); 13[ SerializeField ] GameObject targetObject ; 14int frameStep ; 15bool wasInAnimationMode ; 16float lastAnimTime = - 1f ; 17bool isRecording ; 18 19const string FrameStepPrefKey = "Fold_FrameStep" ; 20 21static Type s_animWindowType ; 22static PropertyInfo s_clipProp , s_timeProp ; 23 24static class Styles 25{ 26public static GUIStyle card ; 27public static GUIStyle cardHeader ; 28public static GUIStyle cardBody ; 29public static GUIStyle miniButtonLeft ; 30public static GUIStyle miniButtonMid ; 31public static GUIStyle miniButtonRight ; 32public static GUIStyle footerButton ; 33 34public static GUIContent iconUp ; 35public static GUIContent iconDown ; 36public static GUIContent iconRemove ; 37public static GUIContent iconAdd ; 38public static GUIContent iconKey ; 39public static GUIContent iconRead ; 40public static GUIContent iconDeleteKey ; 41public static GUIContent iconClear ; 42public static GUIContent iconPrev ; 43public static GUIContent iconNext ; 44 45public static void Init () 46{ 47if ( card != null ) return ; 48 49card = new GUIStyle ( EditorStyles . helpBox ); 50card . padding = new RectOffset ( 1 , 1 , 1 , 1 ); 51card . margin = new RectOffset ( 4 , 4 , 4 , 4 ); 52 53cardHeader = new GUIStyle ( EditorStyles . toolbar ); 54cardHeader . fontStyle = FontStyle . Bold ; 55cardHeader . alignment = TextAnchor . MiddleLeft ; 56cardHeader . padding = new RectOffset ( 5 , 5 , 0 , 0 ); 57cardHeader . fixedHeight = 24 ; 58 59cardBody = new GUIStyle ( EditorStyles . inspectorDefaultMargins ); 60cardBody . padding = new RectOffset ( 10 , 10 , 10 , 10 ); 61 62miniButtonLeft = EditorStyles . miniButtonLeft ; 63miniButtonMid = EditorStyles . miniButtonMid ; 64miniButtonRight = EditorStyles . miniButtonRight ; 65 66footerButton = new GUIStyle ( EditorStyles . miniButton ); 67footerButton . fixedHeight = 24 ; 68footerButton . fixedWidth = 32 ; 69 70iconUp = EditorGUIUtility . IconContent ( "d_scrollup@2x" ); 71iconDown = EditorGUIUtility . IconContent ( "d_scrolldown@2x" ); 72iconRemove = EditorGUIUtility . IconContent ( "TreeEditor.Trash" ); 73iconAdd = EditorGUIUtility . IconContent ( "Toolbar Plus" ); 74iconKey = EditorGUIUtility . IconContent ( "Animation.Record" ); 75iconRead = EditorGUIUtility . IconContent ( "d_Import" ); 76iconDeleteKey = EditorGUIUtility . IconContent ( "d_Toolbar Minus" ); 77iconClear = EditorGUIUtility . IconContent ( "TreeEditor.Trash" ); 78iconPrev = EditorGUIUtility . IconContent ( "Animation.PrevKey" ); 79iconNext = EditorGUIUtility . IconContent ( "Animation.NextKey" ); 80 81if ( iconUp . image == null ) iconUp . text = "▲" ; 82if ( iconDown . image == null ) iconDown . text = "▼" ; 83if ( iconKey . image == null ) iconKey . text = "Rec" ; 84if ( iconRead . image == null ) iconRead . text = "Load" ; 85if ( iconDeleteKey . image == null ) iconDeleteKey . text = "-" ; 86} 87} 88 89static void CacheAnimWindowReflection () 90{ 91if ( s_animWindowType != null ) return ; 92s_animWindowType = typeof ( EditorWindow ). Assembly . GetType ( "UnityEditor.AnimationWindow" ); 93const BindingFlags flags = BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance ; 94s_clipProp = s_animWindowType ? . GetProperty ( "animationClip" , flags ); 95s_timeProp = s_animWindowType ? . GetProperty ( "time" , flags ); 96} 97 98[ MenuItem ( "Tools/yum_food/Fold" )] 99static void ShowWindow () 100{ 101var window = GetWindow < FoldEditorWindow > ( "Fold" ); 102window . minSize = new Vector2 ( 350 , 400 ); 103} 104 105void OnEnable () 106{ 107frameStep = EditorPrefs . GetInt ( FrameStepPrefKey , 1 ); 108EditorApplication . update += OnEditorUpdate ; 109} 110 111void OnDisable () 112{ 113EditorApplication . update -= OnEditorUpdate ; 114EditorPrefs . SetInt ( FrameStepPrefKey , frameStep ); 115ClearPropertyBlock (); 116} 117 118void OnEditorUpdate () 119{ 120if ( isRecording ) return ; 121 122if ( ! AnimationMode . InAnimationMode () || targetMaterial == null ) 123{ 124lastAnimTime = - 1f ; 125return ; 126} 127 128if ( ! TryGetAnimationWindowState ( out var clip , out float time )) return ; 129 130if ( ! Mathf . Approximately ( time , lastAnimTime )) 131{ 132lastAnimTime = time ; 133LoadFromClipPreservingExpanded ( clip , time ); 134Repaint (); 135} 136} 137 138void OnGUI () 139{ 140Styles . Init (); 141 142bool inAnimMode = AnimationMode . InAnimationMode (); 143if ( wasInAnimationMode && ! inAnimMode ) 144ClearPropertyBlock (); 145wasInAnimationMode = inAnimMode ; 146 147EditorGUILayout . Space ( 5 ); 148DrawHeader (); 149EditorGUILayout . Space ( 5 ); 150 151if ( targetMaterial == null ) 152{ 153EditorGUILayout . HelpBox ( "Select a material to build deformation pipelines" , MessageType . Info ); 154return ; 155} 156 157DrawToolbar (); 158 159EditorGUI . BeginChangeCheck (); 160scrollPos = EditorGUILayout . BeginScrollView ( scrollPos ); 161DrawOperationsList (); 162EditorGUILayout . EndScrollView (); 163if ( EditorGUI . EndChangeCheck () && targetMaterial != null ) 164ApplyToMaterial (); 165 166DrawFooter (); 167EditorGUILayout . Space ( 5 ); 168} 169 170void DrawHeader () 171{ 172EditorGUILayout . BeginVertical ( EditorStyles . inspectorDefaultMargins ); 173 174EditorGUILayout . BeginHorizontal (); 175EditorGUILayout . LabelField ( "Target Material" , EditorStyles . boldLabel , GUILayout . Width ( 100 )); 176var newMat = EditorGUILayout . ObjectField ( targetMaterial , typeof ( Material ), false ) as Material ; 177if ( newMat != targetMaterial ) 178{ 179targetMaterial = newMat ; 180if ( targetMaterial != null ) 181LoadFromMaterial (); 182} 183EditorGUILayout . EndHorizontal (); 184 185EditorGUILayout . BeginHorizontal (); 186EditorGUILayout . LabelField ( "Target Object" , EditorStyles . boldLabel , GUILayout . Width ( 100 )); 187targetObject = EditorGUILayout . ObjectField ( targetObject , typeof ( GameObject ), true ) as GameObject ; 188EditorGUILayout . EndHorizontal (); 189 190EditorGUILayout . EndVertical (); 191} 192 193void DrawToolbar () 194{ 195EditorGUILayout . BeginHorizontal ( EditorStyles . toolbar ); 196 197bool pipelineFull = operations . Count >= 16 ; 198using ( new EditorGUI . DisabledScope ( pipelineFull )) 199{ 200var content = pipelineFull 201? new GUIContent ( "Add Operation (Full)" , "Pipeline full (16/16)" ) 202: new GUIContent ( " Add Operation" , Styles . iconAdd . image ); 203if ( GUILayout . Button ( content , EditorStyles . toolbarDropDown , GUILayout . Width ( 130 ))) 204ShowAddOperationMenu (); 205} 206 207GUILayout . FlexibleSpace (); 208EditorGUILayout . EndHorizontal (); 209} 210 211void DrawOperationsList () 212{ 213if ( operations . Count == 0 ) 214{ 215GUILayout . BeginVertical ( Styles . card ); 216GUILayout . Label ( "Pipeline is empty." , EditorStyles . centeredGreyMiniLabel ); 217GUILayout . EndVertical (); 218return ; 219} 220 221for ( int i = 0 ; i < operations . Count ; i ++ ) 222DrawOperation ( i ); 223} 224 225void DrawOperation ( int index ) 226{ 227var op = operations [ index ]; 228bool isExpanded = expandedOps . Contains ( index ); 229 230var defaultColor = GUI . backgroundColor ; 231if ( isExpanded ) GUI . backgroundColor = new Color ( 0.9f , 0.95f , 1f ); 232 233EditorGUILayout . BeginVertical ( Styles . card ); 234GUI . backgroundColor = defaultColor ; 235 236Rect headerRect = EditorGUILayout . GetControlRect ( false , 24 ); 237if ( Event . current . type == EventType . Repaint ) 238Styles . cardHeader . Draw ( headerRect , GUIContent . none , false , false , false , false ); 239 240float btnWidth = 24 , btnHeight = 18 ; 241float btnY = headerRect . y + ( headerRect . height - btnHeight ) / 2 ; 242float rightX = headerRect . xMax - 5 ; 243 244Rect removeRect = new Rect ( rightX - btnWidth , btnY , btnWidth , btnHeight ); 245Rect downRect = new Rect ( removeRect . x - btnWidth , btnY , btnWidth , btnHeight ); 246Rect upRect = new Rect ( downRect . x - btnWidth , btnY , btnWidth , btnHeight ); 247 248Rect arrowRect = new Rect ( headerRect . x + 5 , headerRect . y + 4 , 15 , 16 ); 249Rect indexRect = new Rect ( headerRect . x + 20 , headerRect . y + 4 , 30 , 16 ); 250Rect labelRect = new Rect ( headerRect . x + 50 , headerRect . y + 4 , upRect . x - ( headerRect . x + 50 ), 16 ); 251Rect clickRect = new Rect ( headerRect . x , headerRect . y , upRect . x - headerRect . x , headerRect . height ); 252 253if ( GUI . Button ( clickRect , GUIContent . none , GUIStyle . none )) 254{ 255if ( isExpanded ) expandedOps . Remove ( index ); 256else expandedOps . Add ( index ); 257} 258 259GUI . Label ( arrowRect , isExpanded ? "▼" : "▶" , EditorStyles . label ); 260GUI . Label ( indexRect , $"# { index } " , EditorStyles . miniLabel ); 261GUI . Label ( labelRect , op . GetDisplayName (), EditorStyles . boldLabel ); 262 263if ( GUI . Button ( upRect , Styles . iconUp , Styles . miniButtonLeft ) && index > 0 ) 264{ 265operations . RemoveAt ( index ); 266operations . Insert ( index - 1 , op ); 267if ( expandedOps . Remove ( index )) expandedOps . Add ( index - 1 ); 268} 269if ( GUI . Button ( downRect , Styles . iconDown , Styles . miniButtonMid ) && index < operations . Count - 1 ) 270{ 271operations . RemoveAt ( index ); 272operations . Insert ( index + 1 , op ); 273if ( expandedOps . Remove ( index )) expandedOps . Add ( index + 1 ); 274} 275if ( GUI . Button ( removeRect , Styles . iconRemove , Styles . miniButtonRight )) 276{ 277if ( EditorUtility . DisplayDialog ( "Remove Operation" , $"Remove { op . GetDisplayName ()} ?" , "Yes" , "Cancel" )) 278{ 279operations . RemoveAt ( index ); 280expandedOps . Remove ( index ); 281for ( int i = 0 ; i < expandedOps . Count ; i ++ ) 282if ( expandedOps [ i ] > index ) expandedOps [ i ] -- ; 283} 284} 285 286if ( isExpanded ) 287{ 288EditorGUILayout . BeginVertical ( Styles . cardBody ); 289EditorGUIUtility . labelWidth = 140 ; 290op . DrawParameters (); 291EditorGUIUtility . labelWidth = 0 ; 292EditorGUILayout . EndVertical (); 293} 294 295EditorGUILayout . EndVertical (); 296} 297 298void DrawFooter () 299{ 300EditorGUILayout . BeginVertical ( EditorStyles . inspectorDefaultMargins ); 301EditorGUILayout . Space ( 5 ); 302 303EditorGUILayout . BeginHorizontal (); 304if ( GUILayout . Button ( new GUIContent ( Styles . iconRead . image , "Read from Playhead" ), Styles . miniButtonLeft , GUILayout . Width ( 35 ), GUILayout . Height ( 24 ))) 305ReadFromPlayhead (); 306if ( GUILayout . Button ( new GUIContent ( Styles . iconKey . image , "Record Keyframe" ), Styles . miniButtonMid , GUILayout . Width ( 35 ), GUILayout . Height ( 24 ))) 307ApplyToMaterial ( recordKeyframes : true ); 308if ( GUILayout . Button ( new GUIContent ( Styles . iconDeleteKey . image , "Delete Keyframe" ), Styles . miniButtonMid , GUILayout . Width ( 35 ), GUILayout . Height ( 24 ))) 309DeleteKeyframeAtCurrentTime (); 310if ( GUILayout . Button ( new GUIContent ( "Snap" , "Snap to nearest keyframe" ), Styles . miniButtonRight , GUILayout . Width ( 50 ), GUILayout . Height ( 24 ))) 311SnapToNearestKeyframe (); 312 313GUILayout . FlexibleSpace (); 314 315if ( GUILayout . Button ( new GUIContent ( Styles . iconClear . image , "Clear All" ), Styles . footerButton )) 316{ 317if ( EditorUtility . DisplayDialog ( "Clear All Operations" , "Remove all operations from the pipeline?" , "Clear" , "Cancel" )) 318{ 319operations . Clear (); 320expandedOps . Clear (); 321if ( targetMaterial != null ) 322ApplyToMaterial (); 323} 324} 325EditorGUILayout . EndHorizontal (); 326 327EditorGUILayout . Space ( 4 ); 328 329EditorGUILayout . BeginHorizontal (); 330if ( GUILayout . Button ( new GUIContent ( Styles . iconPrev . image , "Step Back" ), Styles . miniButtonLeft , GUILayout . Width ( 35 ), GUILayout . Height ( 24 ))) 331AdvancePlayhead ( - frameStep ); 332var centerField = new GUIStyle ( EditorStyles . numberField ) { alignment = TextAnchor . MiddleCenter , fixedHeight = 24 }; 333frameStep = EditorGUILayout . IntField ( frameStep , centerField , GUILayout . Width ( 40 )); 334if ( frameStep < 1 ) frameStep = 1 ; 335if ( GUILayout . Button ( new GUIContent ( Styles . iconNext . image , "Step Forward" ), Styles . miniButtonRight , GUILayout . Width ( 35 ), GUILayout . Height ( 24 ))) 336AdvancePlayhead ( frameStep ); 337GUILayout . FlexibleSpace (); 338EditorGUILayout . EndHorizontal (); 339 340EditorGUILayout . Space ( 5 ); 341EditorGUILayout . LabelField ( $" { operations . Count } / 16 Operations" , EditorStyles . centeredGreyMiniLabel ); 342EditorGUILayout . EndVertical (); 343} 344 345void ShowAddOperationMenu () 346{ 347var menu = new GenericMenu (); 348menu . AddItem ( new GUIContent ( "Tube to Plane" ), false , () => AddOperation ( new TubeToPlaneOp ())); 349menu . AddItem ( new GUIContent ( "Plane to Tube" ), false , () => AddOperation ( new PlaneToTubeOp ())); 350menu . AddItem ( new GUIContent ( "Plane to Hemi-Octahedron" ), false , () => AddOperation ( new PlaneToHemiOctahedronOp ())); 351menu . AddItem ( new GUIContent ( "Hemi-Octahedron to Plane" ), false , () => AddOperation ( new HemiOctahedronToPlaneOp ())); 352menu . AddItem ( new GUIContent ( "Plane to Octahedron" ), false , () => AddOperation ( new PlaneToOctahedronOp ())); 353menu . AddItem ( new GUIContent ( "Octahedron to Plane" ), false , () => AddOperation ( new OctahedronToPlaneOp ())); 354menu . AddSeparator ( "" ); 355menu . AddItem ( new GUIContent ( "Point Align" ), false , () => AddOperation ( new PointAlignOp ())); 356menu . AddItem ( new GUIContent ( "Axis Align" ), false , () => AddOperation ( new AxisAlignOp ())); 357menu . AddSeparator ( "" ); 358menu . AddItem ( new GUIContent ( "Scale" ), false , () => AddOperation ( new ScaleOp ())); 359menu . AddItem ( new GUIContent ( "Translate" ), false , () => AddOperation ( new TranslateOp ())); 360menu . AddItem ( new GUIContent ( "Rotate" ), false , () => AddOperation ( new RotateOp ())); 361menu . AddItem ( new GUIContent ( "Norm Conversion" ), false , () => AddOperation ( new NormConversionOp ())); 362menu . AddItem ( new GUIContent ( "Seal" ), false , () => AddOperation ( new SealOp ())); 363menu . AddSeparator ( "" ); 364menu . AddItem ( new GUIContent ( "Sine Waves" ), false , () => AddOperation ( new SineWavesOp ())); 365menu . AddItem ( new GUIContent ( "FBM" ), false , () => AddOperation ( new FBMOp ())); 366menu . ShowAsContext (); 367} 368 369void AddOperation ( DeformOperation op ) 370{ 371if ( operations . Count >= 16 ) return ; 372operations . Add ( op ); 373expandedOps . Add ( operations . Count - 1 ); 374} 375 376void ApplyToMaterial ( bool recordKeyframes = false ) 377{ 378var builder = FoldPipelineBuilder . Create (). For ( targetMaterial ); 379foreach ( var op in operations ) 380op . ApplyTo ( builder ); 381builder . Apply (); 382 383if ( AnimationMode . InAnimationMode ()) 384ApplyPropertyBlock ( builder ); 385if ( recordKeyframes ) 386RecordAnimationKeyframes ( builder ); 387} 388 389void ApplyPropertyBlock ( FoldPipelineBuilder builder ) 390{ 391if ( targetObject == null ) return ; 392var renderer = targetObject . GetComponent < Renderer > (); 393if ( renderer == null ) return ; 394 395var mpb = new MaterialPropertyBlock (); 396renderer . GetPropertyBlock ( mpb ); 397mpb . SetFloat ( "_Vertex_Deformation_Enabled" , 1f ); 398for ( int i = 0 ; i < 16 ; i ++ ) 399{ 400var slot = builder . GetSlot ( i ); 401if ( slot != null ) slot . ApplyToPropertyBlock ( mpb , i ); 402else FoldSlot . ClearInPropertyBlock ( mpb , i ); 403} 404renderer . SetPropertyBlock ( mpb ); 405} 406 407void ClearPropertyBlock () 408{ 409if ( targetObject == null ) return ; 410var renderer = targetObject . GetComponent < Renderer > (); 411if ( renderer != null ) 412renderer . SetPropertyBlock ( null ); 413} 414 415 #region Animation Recording 416 417void ReadFromPlayhead () 418{ 419if ( ! TryGetAnimationWindowState ( out var clip , out float time )) return ; 420LoadFromClipPreservingExpanded ( clip , time ); 421lastAnimTime = time ; 422ApplyToMaterial (); 423Repaint (); 424} 425 426void LoadFromClipPreservingExpanded ( AnimationClip clip , float time ) 427{ 428var savedExpanded = new List < int > ( expandedOps ); 429LoadFromAnimationClip ( clip , time ); 430expandedOps = savedExpanded ; 431expandedOps . RemoveAll ( i=> i >= operations . Count ); 432} 433 434void RecordAnimationKeyframes ( FoldPipelineBuilder builder ) 435{ 436if ( ! TryGetRecordingState ( out var clip , out float time , out _ , out string path )) return ; 437 438isRecording = true ; 439try 440{ 441Undo . RecordObject ( clip , "Create Fold Keyframe" ); 442SetFloatKey ( clip , path , "material._Vertex_Deformation_Enabled" , 1f , time ); 443 444for ( int i = 0 ; i < 16 ; i ++ ) 445{ 446var slot = builder . GetSlot ( i ); 447var prefix = $"material._Vertex_Deformation_Slot_ { i } _" ; 448SetFloatKey ( clip , path , prefix + "Enabled" , slot != null ? 1f : 0f , time ); 449SetDiscreteKey ( clip , path , prefix + "Opcode" , slot ? . opcode ?? 0 , time ); 450SetFloatKey ( clip , path , prefix + "Float_0" , slot ? . float0 ?? 0f , time ); 451SetFloatKey ( clip , path , prefix + "Float_1" , slot ? . float1 ?? 0f , time ); 452SetFloatKey ( clip , path , prefix + "Float_2" , slot ? . float2 ?? 0f , time ); 453SetFloatKey ( clip , path , prefix + "Float_3" , slot ? . float3 ?? 0f , time ); 454SetVectorKey ( clip , path , prefix + "Vector_0" , slot ? . vec0 ?? Vector4 . zero , time ); 455SetVectorKey ( clip , path , prefix + "Vector_1" , slot ? . vec1 ?? Vector4 . zero , time ); 456SetVectorKey ( clip , path , prefix + "Vector_2" , slot ? . vec2 ?? Vector4 . zero , time ); 457SetVectorKey ( clip , path , prefix + "Vector_3" , slot ? . vec3 ?? Vector4 . zero , time ); 458} 459 460lastAnimTime = time ; 461} 462finally 463{ 464isRecording = false ; 465} 466} 467 468void DeleteKeyframeAtCurrentTime () 469{ 470if ( ! TryGetRecordingState ( out var clip , out float time , out _ , out string path )) return ; 471 472Undo . RecordObject ( clip , "Delete Fold Keyframe" ); 473foreach ( var binding in AnimationUtility . GetCurveBindings ( clip )) 474{ 475if ( binding . type != typeof ( Renderer )) continue ; 476if ( binding . path != path ) continue ; 477if ( ! binding . propertyName . StartsWith ( "material._Vertex_Deformation_" )) continue ; 478 479var curve = AnimationUtility . GetEditorCurve ( clip , binding ); 480if ( curve == null ) continue ; 481 482bool removed = false ; 483for ( int i = curve . length - 1 ; i >= 0 ; i -- ) 484{ 485if ( Mathf . Approximately ( curve . keys [ i ]. time , time )) 486{ 487curve . RemoveKey ( i ); 488removed = true ; 489} 490} 491 492if ( removed ) 493AnimationUtility . SetEditorCurve ( clip , binding , curve . length == 0 ? null : curve ); 494} 495} 496 497bool TryGetRecordingState ( out AnimationClip clip , out float time , out Renderer renderer , out string path ) 498{ 499clip = null ; time = 0f ; renderer = null ; path = "" ; 500if ( targetObject == null || ! AnimationMode . InAnimationMode ()) return false ; 501renderer = targetObject . GetComponent < Renderer > (); 502if ( renderer == null ) return false ; 503if ( ! TryGetAnimationWindowState ( out clip , out time )) return false ; 504var animator = targetObject . GetComponentInParent < Animator > (); 505path = animator != null 506? AnimationUtility . CalculateTransformPath ( renderer . transform , animator . transform ) 507: "" ; 508return true ; 509} 510 511static void SetFloatKey ( AnimationClip clip , string path , string prop , float value , float time ) 512{ 513var binding = EditorCurveBinding . FloatCurve ( path , typeof ( Renderer ), prop ); 514var curve = AnimationUtility . GetEditorCurve ( clip , binding ) ?? new AnimationCurve (); 515AddOrReplaceKey ( curve , time , value ); 516AnimationUtility . SetEditorCurve ( clip , binding , curve ); 517} 518 519static void SetDiscreteKey ( AnimationClip clip , string path , string prop , int value , float time ) 520{ 521var binding = EditorCurveBinding . DiscreteCurve ( path , typeof ( Renderer ), prop ); 522var curve = AnimationUtility . GetEditorCurve ( clip , binding ) ?? new AnimationCurve (); 523AddOrReplaceKey ( curve , time , value ); 524AnimationUtility . SetEditorCurve ( clip , binding , curve ); 525} 526 527static void SetVectorKey ( AnimationClip clip , string path , string prop , Vector4 v , float time ) 528{ 529SetFloatKey ( clip , path , prop + ".x" , v . x , time ); 530SetFloatKey ( clip , path , prop + ".y" , v . y , time ); 531SetFloatKey ( clip , path , prop + ".z" , v . z , time ); 532SetFloatKey ( clip , path , prop + ".w" , v . w , time ); 533} 534 535static void AddOrReplaceKey ( AnimationCurve curve , float time , float value ) 536{ 537for ( int i = curve . length - 1 ; i >= 0 ; i -- ) 538if ( Mathf . Approximately ( curve . keys [ i ]. time , time )) 539curve . RemoveKey ( i ); 540curve . AddKey ( new Keyframe ( time , value )); 541} 542 543void SeekTo ( float time ) 544{ 545SetAnimationWindowTime ( time ); 546lastAnimTime = time ; 547ApplyToMaterial (); 548Repaint (); 549} 550 551void AdvancePlayhead ( int frames ) 552{ 553if ( ! TryGetAnimationWindowState ( out var clip , out float time )) return ; 554SeekTo ( time + frames * ( 1f / clip . frameRate )); 555} 556 557void SnapToNearestKeyframe () 558{ 559if ( ! TryGetAnimationWindowState ( out var clip , out float time )) return ; 560 561float bestTime = time ; 562float bestDist = float . MaxValue ; 563 564foreach ( var binding in AnimationUtility . GetCurveBindings ( clip )) 565{ 566if ( ! binding . propertyName . StartsWith ( "material._Vertex_Deformation_" )) continue ; 567var curve = AnimationUtility . GetEditorCurve ( clip , binding ); 568if ( curve == null ) continue ; 569foreach ( var key in curve . keys ) 570{ 571float dist = Mathf . Abs ( key . time - time ); 572if ( dist > 0f && dist < bestDist ) { bestDist = dist ; bestTime = key . time ; } 573} 574} 575 576if ( bestDist < float . MaxValue ) 577SeekTo ( bestTime ); 578} 579 580static void SetAnimationWindowTime ( float time ) 581{ 582CacheAnimWindowReflection (); 583if ( s_animWindowType == null || s_timeProp == null ) return ; 584var windows = Resources . FindObjectsOfTypeAll ( s_animWindowType ); 585if ( windows . Length == 0 ) return ; 586s_timeProp . SetValue ( windows [ 0 ], time ); 587(( EditorWindow ) windows [ 0 ]). Repaint (); 588} 589 590static bool TryGetAnimationWindowState ( out AnimationClip clip , out float time ) 591{ 592clip = null ; 593time = 0f ; 594CacheAnimWindowReflection (); 595if ( s_animWindowType == null || s_clipProp == null || s_timeProp == null ) return false ; 596var windows = Resources . FindObjectsOfTypeAll ( s_animWindowType ); 597if ( windows . Length == 0 ) return false ; 598var window = windows [ 0 ]; 599clip = s_clipProp . GetValue ( window ) as AnimationClip ; 600time = ( float ) s_timeProp . GetValue ( window ); 601return clip != null ; 602} 603 604 #endregion 605 606void LoadFromMaterial () 607{ 608operations . Clear (); 609expandedOps . Clear (); 610 611for ( int i = 0 ; i < 16 ; i ++ ) 612{ 613var prefix = $"_Vertex_Deformation_Slot_ { i } _" ; 614if ( targetMaterial . GetFloat ( prefix + "Enabled" ) < 0.5f ) break ; 615 616int opcode = targetMaterial . GetInteger ( prefix + "Opcode" ); 617if ( opcode == 0 ) break ; 618 619var slot = new FoldSlot 620{ 621opcode = opcode , 622float0 = targetMaterial . GetFloat ( prefix + "Float_0" ), 623float1 = targetMaterial . GetFloat ( prefix + "Float_1" ), 624float2 = targetMaterial . GetFloat ( prefix + "Float_2" ), 625float3 = targetMaterial . GetFloat ( prefix + "Float_3" ), 626vec0 = targetMaterial . GetVector ( prefix + "Vector_0" ), 627vec1 = targetMaterial . GetVector ( prefix + "Vector_1" ), 628vec2 = targetMaterial . GetVector ( prefix + "Vector_2" ), 629vec3 = targetMaterial . GetVector ( prefix + "Vector_3" ), 630}; 631 632var op = DeformOperation . Create ( slot ); 633if ( op == null ) break ; 634operations . Add ( op ); 635} 636} 637 638void LoadFromAnimationClip ( AnimationClip clip , float time ) 639{ 640if ( targetObject == null ) return ; 641var renderer = targetObject . GetComponent < Renderer > (); 642if ( renderer == null ) return ; 643 644var animator = targetObject . GetComponentInParent < Animator > (); 645string path = animator != null 646? AnimationUtility . CalculateTransformPath ( renderer . transform , animator . transform ) 647: "" ; 648 649operations . Clear (); 650 651for ( int i = 0 ; i < 16 ; i ++ ) 652{ 653var prefix = $"material._Vertex_Deformation_Slot_ { i } _" ; 654 655float enabled = SampleFloatCurve ( clip , path , prefix + "Enabled" , time ); 656if ( enabled < 0.5f ) break ; 657 658int opcode = SampleDiscreteCurve ( clip , path , prefix + "Opcode" , time ); 659if ( opcode == 0 ) break ; 660 661var slot = new FoldSlot 662{ 663opcode = opcode , 664float0 = SampleFloatCurve ( clip , path , prefix + "Float_0" , time ), 665float1 = SampleFloatCurve ( clip , path , prefix + "Float_1" , time ), 666float2 = SampleFloatCurve ( clip , path , prefix + "Float_2" , time ), 667float3 = SampleFloatCurve ( clip , path , prefix + "Float_3" , time ), 668vec0 = SampleVectorCurve ( clip , path , prefix + "Vector_0" , time ), 669vec1 = SampleVectorCurve ( clip , path , prefix + "Vector_1" , time ), 670vec2 = SampleVectorCurve ( clip , path , prefix + "Vector_2" , time ), 671vec3 = SampleVectorCurve ( clip , path , prefix + "Vector_3" , time ), 672}; 673 674var op = DeformOperation . Create ( slot ); 675if ( op == null ) break ; 676operations . Add ( op ); 677} 678} 679 680static float SampleFloatCurve ( AnimationClip clip , string path , string prop , float time ) 681{ 682var binding = EditorCurveBinding . FloatCurve ( path , typeof ( Renderer ), prop ); 683var curve = AnimationUtility . GetEditorCurve ( clip , binding ); 684return curve ? . Evaluate ( time ) ?? 0f ; 685} 686 687static int SampleDiscreteCurve ( AnimationClip clip , string path , string prop , float time ) 688{ 689var binding = EditorCurveBinding . DiscreteCurve ( path , typeof ( Renderer ), prop ); 690var curve = AnimationUtility . GetEditorCurve ( clip , binding ); 691if ( curve == null || curve . length == 0 ) return 0 ; 692 693int value = Mathf . RoundToInt ( curve . keys [ 0 ]. value ); 694foreach ( var key in curve . keys ) 695{ 696if ( key . time > time ) break ; 697value = Mathf . RoundToInt ( key . value ); 698} 699return value ; 700} 701 702static Vector4 SampleVectorCurve ( AnimationClip clip , string path , string prop , float time ) => 703new Vector4 ( 704SampleFloatCurve ( clip , path , prop + ".x" , time ), 705SampleFloatCurve ( clip , path , prop + ".y" , time ), 706SampleFloatCurve ( clip , path , prop + ".z" , time ), 707SampleFloatCurve ( clip , path , prop + ".w" , time )); 708 709} 710