1 /**
2     2D Parameter
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.param2d;
16 import inochi2d.param.parameters;
17 import inochi2d.param.utils;
18 import inochi2d.core.registry;
19 import inochi2d.core.serde;
20 import inochi2d.core.math;
21 import inochi2d.core.guid;
22 import inochi2d.common;
23 import inochi2d.puppet;
24 import nulib;
25 import numem;
26 
27 /**
28     2D variant of a parameter.
29 */
30 @TypeId("2d", IN_MAKE_TAG!(2, 0))
31 class Parameter2D : Parameter {
32 protected:
33 @nogc:
34 
35     /**
36         Serialize this parameter.
37     */
38     override
39     void onSerialize(ref DataNode object) {
40         super.onSerialize(object);
41         object["min"] = min.serialize();
42         object["max"] = max.serialize();
43         object["defaults"] = defaults.serialize();
44         object["hpoints"] = hpoints.serialize();
45         object["vpoints"] = vpoints.serialize();
46     }
47 
48     /**
49         Deserialize this parameter.
50     */
51     override
52     void onDeserialize(ref DataNode object, ref ModelState state) {
53         super.onDeserialize(object, state);
54         object.tryGetRef(state, min, "min");
55         object.tryGetRef(state, max, "max");
56         object.tryGetRef(state, defaults, "defaults");
57 
58         // 0.8->0.9 upgrades
59         if (state.doUpgrade08) {
60             state.info("0.8->0.9: Upgrading 2D axis mapping...");
61 
62             auto axes = "axis_points" in object;
63             if (axes && (*axes).isArray) {
64                 (*axes).tryGetRef(state, hpoints, 0);
65                 (*axes).tryGetRef(state, vpoints, 1);
66             }
67             return;
68         }
69 
70         object.tryGetRef(state, hpoints, "hpoints");
71         object.tryGetRef(state, vpoints, "vpoints");
72     }
73 
74     /**
75         Finalizes the parameter.
76 
77         Params:
78             puppet =    The parent puppet
79             state =     The state of the deserializer.
80     */
81     override
82     void onFinalize(Puppet puppet, ref ModelState state) {
83         super.onFinalize(puppet, state);
84         value = defaults;
85     }
86 
87 public:
88 
89     /**
90         The current value of this parameter.
91     */
92     vec2 value = vec2(0, 0);
93 
94     /**
95         The previous value of this parameter.
96     */
97     vec2 prev = vec2(0, 0);
98 
99     /**
100         The default value of this parameter.
101     */
102     vec2 defaults = vec2(0, 0);
103 
104     /**
105         The lower bounds of this parameter.
106     */
107     vec2 min = vec2(0, 0);
108 
109     /**
110         The upper bounds of this parameter.
111     */
112     vec2 max = vec2(1, 1);
113 
114     /**
115         Our horizontal keypoints' positions.
116     */
117     vector!float hpoints;
118 
119     /**
120         Our vertical keypoints' positions.
121     */
122     vector!float vpoints;
123 
124     /**
125         The dimensionality of the parameter.
126     */
127     override @property int dimensions() => 2;
128 
129     /**
130         The current value of the parameter.
131     */
132     override @property float[] currentValue() @trusted => value.data[];
133 
134     /**
135         The lower bound of this parameter.
136     */
137     override @property float[] lowerBound() => min.data[];
138 
139     /**
140         The upper bound of this parameter.
141     */
142     override @property float[] upperBound() => max.data[];
143 
144     /**
145         Counts of elements in each axis.
146     */
147     override @property uint[] elementCounts() {
148         static uint[2] counts;
149         counts = [cast(uint)hpoints.length, cast(uint)vpoints.length];
150         return counts[0 .. 2];
151     }
152 
153     /**
154         Construct a new named parameter.
155     */
156     this(string name = null) {
157         float[2] points_init = [0, 1];
158         hpoints = points_init;
159         vpoints = points_init;
160         guid = inNewGUID();
161         this.name = name;
162     }
163 
164     /**
165         Update our bindings with the value of this parameter.
166     */
167     override
168     void update() {
169         if (!active)
170             return;
171 
172         vec2 norm;
173         vec2u index = findKeypointAndNormal(value, norm);
174 
175         foreach (binding; bindings) {
176             binding.apply(index, norm);
177         }
178     }
179 
180     /**
181         Force this parameter to take on the given value.
182 
183         Params:
184             value = The new value this parameter will take on.
185     */
186     void pushValue(vec2 value) {
187         this.value = value;
188     }
189 
190     /**
191         Force this parameter to take on the given value.
192 
193         Params:
194             axis  = The axis along which the value is set.
195             value = The new value this parameter will take on.
196     */
197     void pushValue(ParameterAxis axis, float value) {
198         final switch (axis) {
199         case ParameterAxis.rows:
200             this.value.y = value;
201             break;
202 
203         case ParameterAxis.columns:
204             this.value.x = value;
205             break;
206         }
207     }
208 
209     /**
210         Find the keypoint index of the given position, as well as its normal.
211 
212         Params:
213             pos = A position along our keypoints. Must be within min & max.
214             norm = The given position, normalized within its keypoint.
215 
216         Returns:
217             The index of the keypoint the given position falls on.
218     */
219     vec2i findKeypointAndNormal(vec2 pos, out vec2 norm) {
220         assert(pos.x >= min.x && pos.x <= max.x);
221         assert(pos.y >= min.y && pos.y <= max.y);
222         const x = searchPoints(hpoints, pos.x, norm.x);
223         const y = searchPoints(vpoints, pos.y, norm.y);
224         return vec2i(x, y);
225     }
226 
227     /**
228         Normalize the given position memberwise between min & max.
229     */
230     vec2 normalize(vec2 pos) const {
231         const x = (pos.x - min.x) / (max.x - min.x);
232         const y = (pos.y - min.y) / (max.y - min.y);
233         return vec2(x, y);
234     }
235 
236     /**
237         Linearly interpolate min & max by the given value.
238     */
239     vec2 lerp(vec2 norm) const {
240         const x = .lerp(min.x, max.x, norm.x);
241         const y = .lerp(min.y, max.y, norm.y);
242         return vec2(x, y);
243     }
244 }
245 
246 mixin Register!(Parameter2D, in_param_registry);