yum/3ner

A toon shader for Unity's BIRP.

git clone https://git.yummers.dev/yum/3ner

yumFold: check in baker2591a93

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