yum/3ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum/3ner
2591a93
master
1using System ; 2using System . Collections . Generic ; 3using System . Linq ; 4using UnityEditor ; 5using UnityEngine ; 6 7namespace Yum . Fold 8{ 9// Translate authored keys directly into renderer material curves; no temporal sampling. 10public static class FoldBaker 11{ 12public static AnimationClip BakeBesideSource ( AnimationClip source , Transform root , float speed ) 13{ 14string sourcePath = AssetDatabase . GetAssetPath ( source ); 15if ( string . IsNullOrEmpty ( sourcePath ) || ! sourcePath . StartsWith ( "Assets/" , StringComparison . Ordinal )) 16throw new InvalidOperationException ( "Save the source animation in Assets before baking." ); 17string path = System . IO . Path . ChangeExtension ( sourcePath , null ) + "_Baked.anim" ; 18var existing = AssetDatabase . LoadMainAssetAtPath ( path ); 19if ( ! existing ) return Bake ( source , root , path , speed ); 20if ( ! ( existing is AnimationClip destination )) 21throw new InvalidOperationException ( "The baked output path is occupied by a different asset type: " + path ); 22// Finish conversion before touching the previous bake; preserve its GUID for Animator references. 23string temporary = AssetDatabase . GenerateUniqueAssetPath ( System . IO . Path . ChangeExtension ( sourcePath , null ) + "_BakeTemp.anim" ); 24var result = Bake ( source , root , temporary , speed ); 25try 26{ 27var oldMaterials = AssetDatabase . LoadAllAssetsAtPath ( path ). OfType < Material > (). ToArray (); 28EditorUtility . CopySerialized ( result , destination ); 29destination . name = System . IO . Path . GetFileNameWithoutExtension ( path ); 30foreach ( var material in oldMaterials ) UnityEngine . Object . DestroyImmediate ( material , true ); 31EditorUtility . SetDirty ( destination ); 32AssetDatabase . SaveAssets (); 33return destination ; 34} 35finally { AssetDatabase . DeleteAsset ( temporary ); } 36} 37 38public static AnimationClip Bake ( AnimationClip source , Transform root , string path , float speed = 1 ) 39{ 40if ( ! source || float . IsNaN ( speed ) || speed < 0 || speed > 10 ) 41throw new ArgumentException ( "Choose a clip and a Bake speed from 0 to 10." ); 42var timed = UnityEngine . Object . Instantiate ( source ); 43try 44{ 45float duration = speed > 0 ? source . length / speed : Mathf . Max ( source . length , 1f / 30 ); 46foreach ( var binding in AnimationUtility . GetCurveBindings ( source )) 47{ 48var curve = AnimationUtility . GetEditorCurve ( source , binding ); 49if ( speed == 0 ) 50{ 51float value = binding . isDiscreteCurve ? curve . keys [ 0 ]. value : curve . Evaluate ( 0 ); 52curve = AnimationCurve . Constant ( 0 , duration , value ); 53} 54else 55{ 56var keys = curve . keys ; 57for ( int i = 0 ; i < keys . Length ; i ++ ) 58{ 59keys [ i ]. time /= speed ; 60keys [ i ]. inTangent *= speed ; 61keys [ i ]. outTangent *= speed ; 62} 63curve . keys = keys ; 64} 65AnimationUtility . SetEditorCurve ( timed , binding , curve ); 66} 67foreach ( var binding in AnimationUtility . GetObjectReferenceCurveBindings ( source )) 68{ 69var keys = AnimationUtility . GetObjectReferenceCurve ( source , binding ); 70if ( speed == 0 && keys . Length > 0 ) 71keys = new [] { new ObjectReferenceKeyframe { time = 0 , value = keys [ 0 ]. value }, 72new ObjectReferenceKeyframe { time = duration , value = keys [ 0 ]. value } }; 73else for ( int i = 0 ; i < keys . Length ; i ++ ) keys [ i ]. time /= speed ; 74AnimationUtility . SetObjectReferenceCurve ( timed , binding , keys ); 75} 76var events = speed == 0 ? new AnimationEvent [ 0 ] : AnimationUtility . GetAnimationEvents ( source ); 77foreach ( var entry in events ) entry . time /= speed ; 78AnimationUtility . SetAnimationEvents ( timed , events ); 79var settings = AnimationUtility . GetAnimationClipSettings ( source ); 80settings . startTime = speed > 0 ? settings . startTime / speed : 0 ; 81settings . stopTime = duration ; 82AnimationUtility . SetAnimationClipSettings ( timed , settings ); 83return BakeCurves ( timed , root , path ); 84} 85finally { UnityEngine . Object . DestroyImmediate ( timed ); } 86} 87 88static AnimationClip BakeCurves ( AnimationClip source , Transform root , string path ) 89{ 90if ( ! root ) throw new ArgumentException ( "Choose the animation root." ); 91if ( ! path . StartsWith ( "Assets/" , StringComparison . Ordinal ) || ! path . EndsWith ( ".anim" , StringComparison . OrdinalIgnoreCase ) || AssetDatabase . LoadMainAssetAtPath ( path )) 92throw new ArgumentException ( "Choose a new .anim asset path inside Assets." ); 93var bindings = AnimationUtility . GetCurveBindings ( source ); 94var groups = bindings . Where ( b=> b . type == typeof ( FoldMaterialAnimator )). GroupBy ( b=> b . path ). ToArray (); 95if ( groups . Length == 0 ) throw new InvalidOperationException ( "This clip has no Fold animation tracks to bake." ); 96var baked = UnityEngine . Object . Instantiate ( source ); 97baked . name = System . IO . Path . GetFileNameWithoutExtension ( path ); 98bool created = false ; 99try 100{ 101var destinations = new HashSet < string > (); 102foreach ( var group in groups ) 103{ 104var transform = string . IsNullOrEmpty ( group . Key ) ? root : root . Find ( group . Key ); 105var driver = transform ? transform . GetComponent < FoldMaterialAnimator > () : null ; 106if ( ! driver || ! driver . targetRenderer || ! driver . material ) 107throw new InvalidOperationException ( "Missing Fold driver, renderer or material at " + group . Key ); 108var renderer = driver . targetRenderer ; 109if ( renderer . transform != root && ! renderer . transform . IsChildOf ( root )) 110throw new InvalidOperationException ( "The Fold renderer must be under Animation root." ); 111var materials = renderer . sharedMaterials ; 112if ( materials . Length != 1 || driver . materialIndex != 0 ) 113throw new InvalidOperationException ( "Direct material animation requires one material per renderer. Split the material slots onto separate renderers before baking: " + renderer . name ); 114if ( materials [ 0 ] != driver . material ) 115throw new InvalidOperationException ( "The Fold material slot has changed. Reinitialize the stack before baking." ); 116if ( ! driver . material . HasProperty ( FoldOperator . Prefix ( 0 ) + "AnimatedOpcode" )) 117throw new InvalidOperationException ( "Update to the Fold shader with AnimatedOpcode properties before baking." ); 118string rendererPath = AnimationUtility . CalculateTransformPath ( renderer . transform , root ); 119if ( AnimationUtility . GetObjectReferenceCurveBindings ( source ). Any ( b=> b . path == rendererPath && b . propertyName . StartsWith ( "m_Materials" , StringComparison . Ordinal ))) 120throw new InvalidOperationException ( "Remove conflicting material-swap tracks before baking: " + renderer . name ); 121foreach ( var binding in group ) 122{ 123var match = System . Text . RegularExpressions . Regex . Match ( binding . propertyName , 124@"^slot(\d+)_(enabled|opcode|float[0-3]|vector[0-3]\.[xyzw])$" ); 125if ( ! match . Success || ! int . TryParse ( match . Groups [ 1 ]. Value , out int slot ) || slot >= 16 ) 126throw new InvalidOperationException ( "Unknown Fold track: " + binding . propertyName ); 127string field = match . Groups [ 2 ]. Value ; 128string property = field == "enabled" ? "Enabled" : field == "opcode" ? "AnimatedOpcode" : 129field . StartsWith ( "float" , StringComparison . Ordinal ) ? "Float_" + field [ 5 ] : "Vector_" + field . Substring ( 6 ); 130property = "material." + FoldOperator . Prefix ( slot ) + property ; 131if ( ! destinations . Add ( rendererPath + ":" + property ) || bindings . Any ( b=> b . path == rendererPath && b . propertyName == property )) 132throw new InvalidOperationException ( "Conflicting material curve: " + property ); 133var curve = AnimationUtility . GetEditorCurve ( source , binding ); 134if ( field == "opcode" ) 135{ 136var keys = curve . keys ; 137for ( int i = 0 ; i < keys . Length ; i ++ ) 138{ 139keys [ i ]. value = BitConverter . ToInt32 ( BitConverter . GetBytes ( keys [ i ]. value ), 0 ); 140keys [ i ]. inTangent = keys [ i ]. outTangent = float . PositiveInfinity ; 141} 142curve . keys = keys ; 143for ( int i = 0 ; i < keys . Length ; i ++ ) 144{ 145AnimationUtility . SetKeyLeftTangentMode ( curve , i , AnimationUtility . TangentMode . Constant ); 146AnimationUtility . SetKeyRightTangentMode ( curve , i , AnimationUtility . TangentMode . Constant ); 147} 148} 149AnimationUtility . SetEditorCurve ( baked , binding , null ); 150AnimationUtility . SetEditorCurve ( baked , EditorCurveBinding . FloatCurve ( rendererPath , renderer . GetType (), property ), curve ); 151} 152} 153AssetDatabase . CreateAsset ( baked , path ); 154created = true ; 155AssetDatabase . SaveAssets (); 156return baked ; 157} 158catch 159{ 160if ( created ) AssetDatabase . DeleteAsset ( path ); 161else UnityEngine . Object . DestroyImmediate ( baked ); 162throw ; 163} 164} 165} 166}