1 /**
2     Deformation information.
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 */
13 module inochi2d.core.math.deform;
14 import inochi2d.core;
15 import inochi2d.core.math;
16 import inochi2d.common;
17 import inochi2d;
18 import nulib.collections.vector;
19 import numem;
20 
21 /**
22     Interface implemented by types which can be deformed.
23 */
24 interface IDeformable {
25 @nogc:
26 public:
27 
28     /**
29         The base position of the deformable's points.
30     */
31     @property const(vec2)[] basePoints();
32 
33     /**
34         The points which may be deformed by the deformer.
35     */
36     @property vec2[] deformPoints();
37 
38     /**
39         The base matrix of the object before any parameters have been applied.
40     */
41     @property Basis deformBaseMatrix();
42 
43     /**
44         World matrix of the deformable object.
45     */
46     @property Basis deformMatrix();
47 
48     /**
49         Deforms the IDeformable.
50 
51         Params:
52             deformed =  The deformation delta.
53             absolute =  Whether the deformation is absolute,
54                         replacing the original deformation.
55     */
56     void deform(vec2[] deformed, bool absolute = false);
57 
58     /**
59         Deforms a single vertex in the IDeformable
60 
61         Params:
62             offset =    The offset into the point list to deform.
63             deform =    The deformation delta.
64             absolute =  Whether the deformation is absolute,
65                         replacing the original deformation.
66     */
67     void deform(size_t offset, vec2 deform, bool absolute = false);
68 
69     /**
70         Resets the deformation for the IDeformable.
71     */
72     void resetDeform();
73 }
74 
75 /**
76     A deformation
77 */
78 struct Deformation {
79     vec2[] vertexOffsets;
80 
81     this(ref return scope Deformation other) @trusted @nogc nothrow pure {
82         this.vertexOffsets = vertexOffsets.nu_dup();
83     }
84 
85     this(vec2[] data) @trusted @nogc nothrow pure {
86         this.update(data);
87     }
88 
89     void update(vec2[] points) @trusted @nogc nothrow pure {
90         vertexOffsets = vertexOffsets.nu_resize(points.length);
91         vertexOffsets[0 .. points.length] = points[0 .. $];
92     }
93 
94     void clear(size_t length) @trusted @nogc nothrow pure {
95         vertexOffsets = vertexOffsets.nu_resize(length);
96         vertexOffsets[0 .. $] = vec2.zero;
97     }
98 
99     Deformation opUnary(string op : "-")() @trusted nothrow pure {
100         Deformation new_;
101 
102         new_.vertexOffsets.length = vertexOffsets.length;
103         foreach (i; 0 .. vertexOffsets.length) {
104             new_.vertexOffsets[i] = -vertexOffsets[i];
105         }
106 
107         return new_;
108     }
109 
110     Deformation opBinary(string op : "*", T)(T other) @trusted @nogc nothrow pure {
111         static if (is(T == Deformation)) {
112             Deformation new_;
113 
114             new_.clear(this.vertexOffsets.length);
115             foreach (i; 0 .. this.vertexOffsets.length) {
116                 new_.vertexOffsets[i] = this.vertexOffsets[i] * other.vertexOffsets[i];
117             }
118 
119             return new_;
120         } else static if (is(T == vec2)) {
121             Deformation new_;
122 
123             new_.clear(this.vertexOffsets.length);
124             foreach (i; 0 .. this.vertexOffsets.length) {
125                 new_.vertexOffsets[i] = this.vertexOffsets[i] * other;
126             }
127 
128             return new_;
129         } else {
130             Deformation new_;
131 
132             new_.clear(this.vertexOffsets.length);
133             foreach (i; 0 .. this.vertexOffsets.length) {
134                 new_.vertexOffsets[i] = this.vertexOffsets[i] * other;
135             }
136 
137             return new_;
138         }
139     }
140 
141     Deformation opBinaryRight(string op : "*", T)(T other) @trusted nothrow pure {
142         static if (is(T == mat4)) {
143 
144             // Optimized matrix multiplication
145             Deformation new_;
146             new_.vertexOffsets = self.vertexOffsets.nu_dup();
147             simd_mul(new_.vertexOffsets, other);
148 
149             return new_;
150         }
151         static if (is(T == Deformation)) {
152             Deformation new_;
153 
154             new_.clear(this.vertexOffsets.length);
155             foreach (i; 0 .. this.vertexOffsets.length) {
156                 new_.vertexOffsets[i] = other.vertexOffsets[i] * this.vertexOffsets[i];
157             }
158 
159             return new_;
160         } else static if (is(T == vec2)) {
161             Deformation new_;
162 
163             new_.clear(this.vertexOffsets.length);
164             foreach (i; 0 .. this.vertexOffsets.length) {
165                 new_.vertexOffsets[i] = other * this.vertexOffsets[i];
166             }
167 
168             return new_;
169         } else {
170             Deformation new_;
171 
172             new_.clear(this.vertexOffsets.length);
173             foreach (i; 0 .. this.vertexOffsets.length) {
174                 new_.vertexOffsets[i] = other * this.vertexOffsets[i];
175             }
176 
177             return new_;
178         }
179     }
180 
181     Deformation opBinary(string op : "+", T)(T other) @trusted nothrow pure {
182         static if (is(T == Deformation)) {
183             Deformation new_;
184 
185             new_.clear(this.vertexOffsets.length);
186             foreach (i; 0 .. this.vertexOffsets.length) {
187                 new_.vertexOffsets[i] = this.vertexOffsets[i] + other.vertexOffsets[i];
188             }
189 
190             return new_;
191         } else static if (is(T == vec2)) {
192             Deformation new_;
193 
194             new_.clear(this.vertexOffsets.length);
195             foreach (i; 0 .. this.vertexOffsets.length) {
196                 new_.vertexOffsets[i] = this.vertexOffsets[i] + other;
197             }
198 
199             return new_;
200         } else {
201             Deformation new_;
202 
203             new_.clear(this.vertexOffsets.length);
204             foreach (i; 0 .. this.vertexOffsets.length) {
205                 new_.vertexOffsets[i] = this.vertexOffsets[i] + other;
206             }
207 
208             return new_;
209         }
210     }
211 
212     Deformation opBinary(string op : "-", T)(T other) @trusted nothrow pure {
213         static if (is(T == Deformation)) {
214             Deformation new_;
215 
216             new_.clear(this.vertexOffsets.length);
217             foreach (i; 0 .. this.vertexOffsets.length) {
218                 new_.vertexOffsets[i] = this.vertexOffsets[i] - other.vertexOffsets[i];
219             }
220 
221             return new_;
222         } else static if (is(T == vec2)) {
223             Deformation new_;
224 
225             new_.clear(this.vertexOffsets.length);
226             foreach (i; 0 .. this.vertexOffsets.length) {
227                 new_.vertexOffsets[i] = this.vertexOffsets[i] - other;
228             }
229 
230             return new_;
231         } else {
232             Deformation new_;
233 
234             new_.clear(this.vertexOffsets.length);
235             foreach (i; 0 .. this.vertexOffsets.length) {
236                 new_.vertexOffsets[i] = this.vertexOffsets[i] - other;
237             }
238 
239             return new_;
240         }
241     }
242 
243     void onSerialize(ref DataNode data) @nogc {
244         foreach (offset; vertexOffsets) {
245             data ~= offset.serialize();
246         }
247     }
248 
249     void onDeserialize(ref DataNode data, ref ModelState state) @nogc {
250         if (state.doUpgrade08) {
251 
252             this.vertexOffsets = nu_malloca!vec2(data.length);
253             foreach (i, ref element; data.array) {
254                 this.vertexOffsets[i / 2] = element.deserialize!vec2(state);
255             }
256             return;
257         }
258     }
259 
260     void free() @nogc {
261         if (vertexOffsets)
262             nu_freea(vertexOffsets);
263     }
264 }