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 }