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 param.pushIOffsetAxis(axis, value); 72 } 73 74 /** 75 Serializes a parameter 76 */ 77 void serialize(S)(ref S serializer) { 78 auto state = serializer.objectBegin; 79 serializer.putKey("param"); 80 serializer.putValue(param.name); 81 serializer.putKey("axis"); 82 serializer.putValue(axis); 83 serializer.putKey("range"); 84 range.serialize(serializer); 85 serializer.objectEnd(state); 86 } 87 88 /** 89 Deserializes a parameter 90 */ 91 SerdeException deserializeFromFghj(Fghj data) { 92 data["param"].deserializeValue(this.paramId); 93 data["axis"].deserializeValue(this.axis); 94 this.range.deserialize(data["axis"]); 95 return null; 96 } 97 98 void reconstruct(Puppet puppet) { } 99 100 void finalize(Puppet puppet) { 101 foreach(ref parameter; puppet.parameters) { 102 if (parameter.name == paramId) { 103 param = parameter; 104 return; 105 } 106 } 107 } 108 109 } 110 111 class Automation { 112 private: 113 @Ignore 114 Puppet parent; 115 116 protected: 117 118 @Ignore 119 AutomationBinding[] bindings; 120 121 /** 122 Helper function to remap range from 0.0-1.0 123 to min-max 124 */ 125 final 126 float remapRange(float value, vec2 range) { 127 return range.x + value * (range.y - range.x); 128 } 129 130 /** 131 Called on update to update a single binding. 132 133 Use currTime() to get the current time 134 Use deltaTime() to get delta time 135 Use binding.range to get the range to apply the automation within. 136 */ 137 void onUpdate() { } 138 139 void serializeSelf(ref InochiSerializer serializer) { } 140 void deserializeSelf(Fghj data) { } 141 142 public: 143 /** 144 Human readable name of automation 145 */ 146 string name; 147 148 /** 149 Whether the automation is enabled 150 */ 151 bool enabled = true; 152 153 /** 154 Type ID of the automation 155 */ 156 string typeId; 157 158 /** 159 Instantiates a new Automation 160 */ 161 this(Puppet parent) { 162 this.parent = parent; 163 } 164 165 /** 166 Adds a binding 167 */ 168 void bind(AutomationBinding binding) { 169 this.bindings ~= binding; 170 } 171 172 173 void reconstruct(Puppet puppet) { 174 foreach(ref binding; bindings.dup) { 175 binding.reconstruct(parent); 176 } 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 }