1 /**
2     Specific Parameter Implementations
3 
4     Copyright: 
5         Copyright © 2020-2026, Inochi2D Project
6     
7     License:
8         $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License)
9 
10     Authors:
11         Luna Nielsen
12         Mireille Arseneault
13         Hoshino Lina
14 */
15 module inochi2d.param.parameters;
16 import inochi2d.param.bindings;
17 import inochi2d.param.utils;
18 import inochi2d.common;
19 import inochi2d.puppet;
20 import inochi2d.nodes;
21 import inochi2d.core;
22 import inochi2d.core.serde;
23 
24 import numem;
25 import numem.core.memory;
26 
27 import nulib.collections;
28 import nulib.string;
29 
30 import numath;
31 
32 /**
33     The public parameter registry.
34 */
35 __gshared TypeRegistry!Parameter in_param_registry;
36 
37 public import inochi2d.param.parameters.param1d;
38 public import inochi2d.param.parameters.param2d;
39 
40 /**
41     Top-level parameter deserialization function.
42 */
43 Parameter tryDeserializeParam(ref DataNode object, ref ModelState state) @nogc {
44     if (state.doUpgrade08) {
45         state.info(nstring("0.8->0.9: upgrading legacy parameter ", object["name"].text));
46         auto param = object.tryGet!bool(state, "is_vec2", false) ?
47             nogc_new!Parameter2D() : nogc_new!Parameter1D();
48 
49         param.deserialize(object, state);
50         return param;
51     }
52 
53     if (auto param = in_param_registry.tryCreateFrom(object)) {
54         param.deserialize(object, state);
55         return param;
56     }
57 
58     state.warning(nstring("Encountered untyped parameter, ignoring..."));
59     return null;
60 }
61 
62 /**
63     Parameters are configurable values that are used to drive mesh
64         deformations, property overrides, and more.
65 */
66 abstract
67 class Parameter : NuRefCounted, ISerializable, IDeserializable!ModelState {
68 protected:
69 @nogc:
70 
71     /**
72         Serialize this parameter.
73     */
74     override
75     void onSerialize(ref DataNode object) {
76         object["guid"] = guid.toString()[];
77         object["name"] = name[];
78         // object["bindings"] = bindings.serialize();
79     }
80 
81     /**
82         Deserialize this parameter.
83     */
84     override
85     void onDeserialize(ref DataNode object, ref ModelState state) {
86         guid = object.tryGetGUID(state, "uuid");
87         object.tryGetRef(state, name, "name");
88 
89         auto pbindings = "bindings" in object;
90         if (pbindings && (*pbindings).isArray) {
91             foreach (ref binding; (*pbindings).array) {
92                 this.bindings ~= binding.tryDeserializeBinding(state, this);
93             }
94         }
95     }
96 
97     /**
98         Finalizes the parameter.
99 
100         Params:
101             puppet =    The parent puppet
102             state =     The state of the deserializer.
103     */
104     void onFinalize(Puppet puppet, ref ModelState state) {
105         foreach_reverse (i; 0 .. bindings.length) {
106             if (auto binding = bindings[i]) {
107                 binding.finalize(puppet, state);
108             } else {
109                 bindings.removeAt(i);
110             }
111         }
112     }
113 
114 public:
115 
116     /**
117         The globally unique ID of this parameter.
118     */
119     GUID guid;
120 
121     /**
122         The user-facing name of this parameter.
123     */
124     nstring name;
125 
126     /**
127         Whether this parameter currently updates the model.
128     */
129     bool active = true;
130 
131     /**
132         The bindings of this parameter to puppet nodes.
133     */
134     vector!ParameterBinding bindings;
135 
136     /**
137         The dimensionality of the parameter.
138     */
139     abstract @property int dimensions();
140 
141     /**
142         Counts of elements in each axis.
143     */
144     abstract @property uint[] elementCounts();
145 
146     /**
147         The current value of the parameter.
148     */
149     abstract @property float[] currentValue();
150 
151     /**
152         The lower bound of this parameter.
153     */
154     abstract @property float[] lowerBound();
155 
156     /**
157         The upper bound of this parameter.
158     */
159     abstract @property float[] upperBound();
160 
161     /**
162         Check whether this parameter has a binding to the given target.
163 
164         Params:
165             node = 
166             prop = 
167 
168         Returns:
169             $(D true) if this parameter has a binding to the target,
170             $(D false) otherwise.
171     */
172     bool hasBinding(Node node, string prop) {
173         //foreach (ref binding; bindings) {
174         //    if (binding.target.node is node && binding.target.prop == prop) {
175         //        return true;
176         //    }
177         //}
178 
179         return false;
180     }
181 
182     /**
183         Check whether this parameter has any bindings to the given node.
184 
185         Params:
186             node = 
187 
188         Returns:
189             $(D true) if this parameter has any bindings to the node,
190             $(D false) otherwise.
191     */
192     bool hasAnyBindingsTo(Node node) {
193         //foreach (binding; bindings) {
194         //    if (binding.target.node is node) {
195         //        return true;
196         //    }
197         //}
198 
199         return false;
200     }
201 
202     /**
203         Serializes this parameter.
204     */
205     final void serialize(ref DataNode object) {
206         this.onSerialize(object);
207     }
208 
209     /**
210         Deserializes this parameter.
211     */
212     final void deserialize(ref DataNode object, ref ModelState state) {
213         this.onDeserialize(object, state);
214     }
215 
216     /**
217         Finalizes the parameter.
218 
219         Params:
220             puppet =    The parent puppet
221             state =     The state of the deserializer.
222     */
223     final void finalize(Puppet puppet, ref ModelState state) {
224         this.onFinalize(puppet, state);
225     }
226 
227     /**
228         Update our bindings with the value of this parameter.
229     */
230     abstract void update();
231 }
232 
233 enum ParameterAxis {
234     /**
235         Axis along rows (vertical).
236     */
237     rows = 0,
238 
239     /**
240         Axis along columns (horizontal).
241     */
242     columns = 1,
243 }
244 
245 enum ParameterMergeMode {
246     /**
247         Parameters are merged additively
248     */
249     additive = 0x00,
250 
251     /**
252         Parameters are merged with a weighted average
253     */
254     weighted = 0x01,
255 
256     /**
257         Parameters are merged multiplicatively
258     */
259     multiplicative = 0x02,
260 
261     /**
262         Forces parameter to be given value
263     */
264     forced = 0x03,
265 
266     /**
267         Merge mode is passthrough
268     */
269     passthrough = 0x04,
270 }
271 
272 /**
273     Gets a parameter merge mode from its string name.
274 */
275 ParameterMergeMode toParameterMergeMode(string value) @nogc {
276     switch (value) {
277     case "additive":
278     case "Additive":
279         return ParameterMergeMode.additive;
280 
281     case "weighted":
282     case "Weighted":
283         return ParameterMergeMode.weighted;
284 
285     case "multiplicative":
286     case "Multiplicative":
287         return ParameterMergeMode.multiplicative;
288 
289     case "forced":
290     case "Forced":
291         return ParameterMergeMode.forced;
292 
293     default:
294     case "passthrough":
295     case "Passthrough":
296         return ParameterMergeMode.passthrough;
297     }
298 }
299 
300 /**
301     Find the index and normal of the given position among the given points.
302 
303     Params:
304         points = The set of points to search, must contain at least two values.
305         pos = The position to search for among the given points.
306         norm = The given position, normalized between its two adjacent points.
307 
308     Returns:
309         The index of the point right *before* the given position,
310         or $(D -1) if not found.
311 */
312 ptrdiff_t searchPoints(float[] points, float pos, out float norm) pure @nogc {
313 
314     // Find index of given position.
315     const index = searchPoints(points, pos);
316 
317     if (index >= 0) {
318 
319         // Normalize along two adjacent points.
320         const lo = points[index];
321         const hi = points[index + 1];
322         norm = (pos - lo) / (hi - lo);
323     }
324 
325     return index;
326 }
327 
328 /**
329     Find the index of the given position among the given points.
330 
331     Params:
332         points = The set of points to search, must contain at least two values.
333         pos = The position for which to search among the given points.
334 
335     Returns:
336         The index of the point right *before* the given position,
337         or $(D -1) if not found.
338 */
339 ptrdiff_t searchPoints(float[] points, float pos) pure @nogc {
340     assert(points.length >= 2, "Cannot search lists of points with fewer than 2 elements.");
341 
342     // Binary-search points list for our position.
343     auto cursor = points[0 .. $ - 1];
344     while (cursor.length > 1) {
345         if (pos < cursor[$ / 2]) {
346             cursor = cursor[0 .. $ / 2];
347         } else {
348             cursor = cursor[$ / 2 .. $];
349         }
350     }
351 
352     // Pointer distance from points start to cursor start.
353     return cast(ptrdiff_t)(&cursor[0] - &points[0]);
354 }