using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; namespace Yum.Fold { [AttributeUsage(AttributeTargets.Field)] public abstract class JumpbackAttribute : Attribute { public abstract object Apply(object authored); } public sealed class CopyAttribute : JumpbackAttribute { public override object Apply(object authored) => authored; } public sealed class ZeroAttribute : JumpbackAttribute { public override object Apply(object authored) => authored is Vector4 v ? (object)new Vector4(0, 0, 0, v.w) : 0f; } public sealed class OneAttribute : JumpbackAttribute { public override object Apply(object authored) => authored is Vector4 v ? (object)new Vector4(1, 1, 1, v.w) : 1f; } // Slot maps a named field to its scalar or vector slot in the shader ABI. [AttributeUsage(AttributeTargets.Field)] sealed class SlotAttribute : Attribute { public readonly int index; public SlotAttribute(int index) { this.index = index; } } [AttributeUsage(AttributeTargets.Class)] sealed class OpcodeAttribute : Attribute { public readonly int value; public OpcodeAttribute(int value) { this.value = value; } } public static class FoldOperators { [Opcode(0)] public sealed class None { } [Opcode(1)] public sealed class TubeToPlane { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 RAxis = Vector3.right; [Copy, Slot(2)] public Vector3 SAxis = Vector3.forward; [Zero, Slot(0)] public float T = 1; } [Opcode(2)] public sealed class PlaneToTube { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 RAxis = Vector3.right; [Copy, Slot(2)] public Vector3 SAxis = Vector3.forward; [Zero, Slot(0)] public float T = 1; } [Opcode(3)] public sealed class PointAlign { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 Target = Vector3.up; [Copy, Slot(2)] public Vector3 RAxis = Vector3.right; [Zero, Slot(0)] public float T = 1; } [Opcode(4)] public sealed class AxisAlign { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 Target = Vector3.up; [Copy, Slot(2)] public Vector3 RAxis = Vector3.right; [Zero, Slot(0)] public float T = 1; } [Opcode(5)] public sealed class NormConversion { [Copy, Slot(0)] public float InputNorm = 2; [Copy, Slot(1)] public float OutputNorm = 1; [Zero, Slot(2)] public float T = 1; } [Opcode(6)] public sealed class Seal { [Copy, Slot(0)] public float Amplitude = .1f; [Copy, Slot(1)] public float Frequency = 2; [Copy, Slot(2)] public float TimeMultiplier = .8f; [Zero, Slot(3)] public float T = 1; } [Opcode(7)] public sealed class SineWaves { [Zero, Slot(0)] public Vector3 Amplitude = new Vector3(0, .1f, 0); [Copy, Slot(1)] public Vector3 Direction = Vector3.right; [Copy, Slot(2)] public Vector3 WaveNumber = Vector3.one; [Copy, Slot(3)] public Vector3 AngularVelocity = Vector3.one; } [Opcode(8)] public sealed class FBM { [Zero, Slot(0)] public Vector3 Amplitude = Vector3.one * .1f; [Copy, Slot(1)] public Vector3 Velocity = Vector3.zero; [Copy, Slot(2)] public Vector3 Period = Vector3.one; [Copy, Slot(0)] public float Gain = .5f; [Copy, Slot(1)] public float Lacunarity = 2; [Copy, Slot(2)] public float Octaves = 4; [Copy, Slot(3)] public float Scale = 1; } [Opcode(9)] public sealed class PlaneToHemiOctahedron { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 RAxis = Vector3.right; [Copy, Slot(2)] public Vector3 SAxis = Vector3.forward; [Zero, Slot(0)] public float T = 1; } [Opcode(10)] public sealed class HemiOctahedronToPlane { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 RAxis = Vector3.right; [Copy, Slot(2)] public Vector3 SAxis = Vector3.forward; [Zero, Slot(0)] public float T = 1; } [Opcode(11)] public sealed class Scale { [One, Slot(0)] public Vector3 Factors = Vector3.one; [Zero, Slot(0)] public float T = 1; } [Opcode(12)] public sealed class Translate { [Zero, Slot(0)] public Vector3 Offset = Vector3.zero; [Zero, Slot(0)] public float T = 1; } [Opcode(13)] public sealed class PlaneToOctahedron { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 RAxis = Vector3.right; [Copy, Slot(2)] public Vector3 SAxis = Vector3.forward; [Zero, Slot(0)] public float T = 1; } [Opcode(14)] public sealed class OctahedronToPlane { [Copy, Slot(0)] public Vector3 Origin = Vector3.zero; [Copy, Slot(1)] public Vector3 RAxis = Vector3.right; [Copy, Slot(2)] public Vector3 SAxis = Vector3.forward; [Zero, Slot(0)] public float T = 1; } [Opcode(15)] public sealed class Rotate { [Copy, Slot(0)] public Vector3 Center = Vector3.zero; [Copy, Slot(1)] public Vector3 Axis = Vector3.up; [Copy, Slot(0)] public float Angle = Mathf.PI / 2; // Radians. [Zero, Slot(1)] public float T = 1; } sealed class Definition { public FoldOperator defaults; public readonly Dictionary policies = new Dictionary(); } // Cache mappings once. Serialized slots and animation property names stay stable. static readonly Dictionary Definitions = BuildDefinitions(); static Dictionary BuildDefinitions() { var definitions = new Dictionary(); foreach (var type in typeof(FoldOperators).GetNestedTypes(BindingFlags.Public)) { var opcode = type.GetCustomAttribute(); if (opcode == null) continue; var source = Activator.CreateInstance(type); var definition = new Definition { defaults = new FoldOperator { enabled = true, opcode = opcode.value } }; foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { var slot = field.GetCustomAttribute(); var policy = field.GetCustomAttribute(); bool vector = field.FieldType == typeof(Vector3); if (slot == null || policy == null || slot.index < 0 || slot.index > 3 || (!vector && field.FieldType != typeof(float))) throw new InvalidOperationException($"Invalid Fold field definition: {type.Name}.{field.Name}"); var packed = typeof(FoldOperator).GetField((vector ? "vector" : "float") + slot.index); object value = field.GetValue(source); packed.SetValue(definition.defaults, vector ? (object)(Vector4)(Vector3)value : value); definition.policies.Add(packed, policy); } definitions.Add(opcode.value, definition); } return definitions; } public static FoldOperator Defaults(int opcode) => Definitions.TryGetValue(opcode, out var definition) ? definition.defaults.Copy() : new FoldOperator { enabled = true, opcode = opcode }; public static FoldOperator Jumpback(FoldOperator authored) { // Opcode, enabled, unused fields and vector w components retain their authored values. var result = authored.Copy(); if (Definitions.TryGetValue(authored.opcode, out var definition)) foreach (var entry in definition.policies) entry.Key.SetValue(result, entry.Value.Apply(entry.Key.GetValue(authored))); return result; } } }