1 /* 2 Copyright © 2022, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module inochi2d.core.automation; 8 import inochi2d.math.serialization; 9 import inochi2d; 10 11 /** 12 Automation binding 13 */ 14 struct AutomationBinding { 15 16 /** 17 Used for serialization. 18 Name of parameter 19 */ 20 string paramId; 21 22 /** 23 Parameter to bind to 24 */ 25 Parameter param; 26 27 /** 28 Axis to bind to 29 0 = X 30 1 = Y 31 */ 32 int axis; 33 34 /** 35 Min/max range of binding 36 */ 37 vec2 range; 38 39 /** 40 Gets the value at the specified axis 41 */ 42 float getAxisValue() { 43 switch(axis) { 44 case 0: 45 return param.value.x; 46 case 1: 47 return param.value.y; 48 default: return float.nan; 49 } 50 } 51 52 /** 53 Sets axis value (WITHOUT REMAPPING) 54 */ 55 void setAxisValue(float value) { 56 switch(axis) { 57 case 0: 58 param.value.x = value; 59 break; 60 case 1: 61 param.value.y = value; 62 break; 63 default: assert(0); 64 } 65 } 66 67 /** 68 Sets axis value (WITHOUT REMAPPING) 69 */ 70 void addAxisOffset(float value) { 71 switch(axis) { 72 case 0: 73 param.offset.x += value; 74 break; 75 case 1: 76 param.offset.y += value; 77 break; 78 default: assert(0); 79 } 80 } 81 82 /** 83 Serializes a parameter 84 */ 85 void serialize(S)(ref S serializer) { 86 auto state = serializer.objectBegin; 87 serializer.putKey("param"); 88 serializer.putValue(param.name); 89 serializer.putKey("axis"); 90 serializer.putValue(axis); 91 serializer.putKey("range"); 92 range.serialize(serializer); 93 serializer.objectEnd(state); 94 } 95 96 /** 97 Deserializes a parameter 98 */ 99 SerdeException deserializeFromFghj(Fghj data) { 100 data["param"].deserializeValue(this.paramId); 101 data["axis"].deserializeValue(this.axis); 102 this.range.deserialize(data["axis"]); 103 return null; 104 } 105 106 void finalize(Puppet puppet) { 107 foreach(ref parameter; puppet.parameters) { 108 if (parameter.name == paramId) { 109 param = parameter; 110 return; 111 } 112 } 113 } 114 115 } 116 117 class Automation { 118 private: 119 @Ignore 120 Puppet parent; 121 122 protected: 123 124 @Ignore 125 AutomationBinding[] bindings; 126 127 /** 128 Helper function to remap range from 0.0-1.0 129 to min-max 130 */ 131 final 132 float remapRange(float value, vec2 range) { 133 return range.x + value * (range.y - range.x); 134 } 135 136 /** 137 Called on update to update a single binding. 138 139 Use currTime() to get the current time 140 Use deltaTime() to get delta time 141 Use binding.range to get the range to apply the automation within. 142 */ 143 void onUpdate() { } 144 145 void serializeSelf(ref InochiSerializer serializer) { } 146 void serializeSelf(ref InochiSerializerCompact serializer) { } 147 void deserializeSelf(Fghj data) { } 148 149 public: 150 /** 151 Human readable name of automation 152 */ 153 string name; 154 155 /** 156 Whether the automation is enabled 157 */ 158 bool enabled = true; 159 160 /** 161 Type ID of the automation 162 */ 163 string typeId; 164 165 /** 166 Instantiates a new Automation 167 */ 168 this(Puppet parent) { 169 this.parent = parent; 170 } 171 172 /** 173 Adds a binding 174 */ 175 void bind(AutomationBinding binding) { 176 this.bindings ~= binding; 177 } 178 179 /** 180 Finalizes the loading of the automation 181 */ 182 final void finalize(Puppet parent) { 183 this.parent = parent; 184 foreach(ref binding; bindings) { 185 binding.finalize(parent); 186 } 187 } 188 189 /** 190 Updates and applies the automation to all the parameters 191 that this automation is bound to 192 */ 193 final void update() { 194 if (!enabled) return; 195 this.onUpdate(); 196 } 197 198 /** 199 Serializes a parameter 200 */ 201 void serialize(S)(ref S serializer) { 202 auto state = serializer.objectBegin; 203 serializer.putKey("type"); 204 serializer.serializeValue(typeId); 205 serializer.putKey("name"); 206 serializer.serializeValue(name); 207 serializer.putKey("bindings"); 208 serializer.serializeValue(bindings); 209 this.serializeSelf(serializer); 210 serializer.objectEnd(state); 211 } 212 213 /** 214 Deserializes a parameter 215 */ 216 SerdeException deserializeFromFghj(Fghj data) { 217 data["name"].deserializeValue(this.name); 218 data["bindings"].deserializeValue(this.bindings); 219 this.deserializeSelf(data); 220 return null; 221 } 222 } 223 224 // 225 // SERIALIZATION SHENNANIGANS 226 // 227 228 private { 229 Automation delegate(Puppet parent)[string] typeFactories; 230 } 231 232 void inRegisterAutomationType(T)() if (is(T : Automation)) { 233 import std.traits : getUDAs; 234 typeFactories[getUDAs!(T, TypeId)[0].id] = (Puppet parent) { 235 return new T(parent); 236 }; 237 } 238 239 /** 240 Instantiates automation 241 */ 242 Automation inInstantiateAutomation(string id, Puppet parent) { 243 return typeFactories[id](parent); 244 } 245 246 /** 247 Gets whether a node type is present in the factories 248 */ 249 bool inHasAutomationType(string id) { 250 return (id in typeFactories) !is null; 251 } 252 253 mixin template InAutomation(T) { 254 static this() { 255 inRegisterAutomationType!(T); 256 } 257 }