1 /**
2     Inochi2D Meshes
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.mesh;
14 import inochi2d.core.render.state;
15 import inochi2d.core.serde;
16 import inochi2d.core.math.simd;
17 import inochi2d.core.math.trig;
18 import inochi2d.common;
19 import numath;
20 import numem;
21 
22 version (IN_VEC3_POSITION)
23     alias vtx_t = vec3;
24 else
25     alias vtx_t = vec2;
26 
27 /**
28     Vertex Data that gets submitted to the GPU.
29 */
30 struct VtxData {
31     vtx_t vtx;
32     vec2 uv;
33 }
34 
35 /**
36     A collection of points connected to create a mesh.
37 
38     This is a nogc reimplementation of Inochi2D's mesh
39     handling, made to be more optimal to send to the GPU.
40 */
41 class Mesh : NuRefCounted {
42 private:
43 @nogc:
44     VtxData[] vtx_;
45     uint[] idx_;
46     vec2[] vto_;
47 
48 public:
49 
50     /**
51         The points of the vertices of the mesh.
52     */
53     @property vec2[] points() => vto_[0 .. $];
54 
55     /**
56         The vertex data stored in the mesh.
57     */
58     @property VtxData[] vertices() => vtx_[0 .. $];
59 
60     /**
61         The index data stored in the mesh.
62     */
63     @property uint[] indices() => idx_[0 .. $];
64 
65     /**
66         How many vertices are in the mesh.
67     */
68     @property uint vertexCount() => cast(uint)vtx_.length;
69 
70     /**
71         How many indices are in the mesh.
72     */
73     @property uint elementCount() => cast(uint)idx_.length;
74 
75     /**
76         How many triangles are in the mesh.
77     */
78     @property uint triangleCount() => cast(uint)(idx_.length / 3);
79 
80     /**
81         Bounds of the deformed mesh.
82     */
83     @property rect bounds() => vto_.getBounds();
84 
85     // Destructor
86     ~this() {
87         nu_freea(vtx_);
88         nu_freea(idx_);
89         nu_freea(vto_);
90     }
91 
92     /**
93         Creates an empty mesh.
94     */
95     this() {
96     }
97 
98     /**
99         Creates a mesh from a encoded Inochi2D MeshData
100         structure.
101     */
102     this(ref MeshData meshData) {
103         this.vtx_ = nu_malloca!VtxData(meshData.vertices.length);
104         this.idx_ = meshData.indices.nu_dup();
105         this.vto_ = meshData.vertices.nu_dup();
106 
107         foreach (i; 0 .. vtx_.length) {
108             version (IN_VEC3_POSITION) {
109                 this.vtx_[i] = VtxData(vec3(this.vto_[i], 0), meshData.uvs[i]);
110             } else {
111                 this.vtx_[i] = VtxData(this.vto_[i], meshData.uvs[i]);
112             }
113         }
114     }
115 
116     /**
117         Creates a mesh from a encoded Inochi2D MeshData
118         structure.
119 
120         Params:
121             data =  The mesh data.
122             free =  Whether to free the original mesh data.
123     */
124     static Mesh fromMeshData(ref MeshData data, bool free = true) {
125         auto result = nogc_new!Mesh(data);
126 
127         if (free)
128             data.free();
129 
130         return result;
131     }
132 
133     /**
134         Makes a clone of this mesh.
135 
136         Returns:
137             A new mesh with the data cloned.
138     */
139     Mesh clone() {
140         Mesh result = nogc_new!Mesh();
141         result.vtx_ = this.vtx_.nu_dup();
142         result.idx_ = this.idx_.nu_dup();
143         result.vto_ = this.vto_.nu_dup();
144         return result;
145     }
146 
147     /**
148         Gets the triangle in the mesh at the given offset.
149 
150         Params:
151             offset = The offset into the mesh.
152 
153         Returns:
154             The requested triangle.
155     */
156     Triangle getTriangle(uint offset) {
157         if (offset > idx_.length / 3)
158             return Triangle.init;
159 
160         return Triangle(
161                 vto_[idx_[(offset * 3) + 0]].xy,
162                 vto_[idx_[(offset * 3) + 1]].xy,
163                 vto_[idx_[(offset * 3) + 2]].xy
164         );
165     }
166 
167     /**
168         Gets an array of every triangle in the mesh.
169 
170         Returns:
171             A nogc array of triangles that you must free
172             yourself with $(D nu_freea).
173     */
174     Triangle[] getTriangles() {
175         Triangle[] tris = nu_malloca!Triangle(triangleCount);
176         foreach (i; 0 .. tris.length) {
177             tris[i] = Triangle(
178                     vto_[idx_[(i * 3) + 0]].xy,
179                     vto_[idx_[(i * 3) + 1]].xy,
180                     vto_[idx_[(i * 3) + 2]].xy
181             );
182         }
183         return tris;
184     }
185 
186     /**
187         Frees this mesh.
188     */
189     void free() {
190         auto self = this;
191         nogc_delete(self);
192     }
193 }
194 
195 /**
196     A mesh which recieves deformation data from the outside.
197 */
198 final
199 class DeformedMesh : NuObject {
200 private:
201 @nogc:
202     Mesh parent_;
203     VtxData[] vertices_;
204     vec2[] delta_;
205 
206 public:
207 
208     /**
209         The parent of the deformed mesh.
210     */
211     @property Mesh parent() => parent_;
212     @property void parent(Mesh value) {
213         this.parent_ = value;
214         if (parent_) {
215             this.vertices_ = vertices_.nu_resize(value.points.length);
216             this.delta_ = delta_.nu_resize(value.points.length);
217             this.reset();
218         }
219     }
220 
221     /**
222         The deformed points of the mesh.
223     */
224     @property vec2[] points() => delta_;
225 
226     /**
227         The deformed vertices of the mesh.
228     */
229     @property VtxData[] vertices() => vertices_;
230 
231     /**
232         The indices for the mesh.
233     */
234     @property uint[] indices() => parent.indices;
235 
236     /**
237         How many vertices are in the mesh.
238     */
239     @property uint vertexCount() => cast(uint)vertices_.length;
240 
241     /**
242         How many indices are in the mesh.
243     */
244     @property uint elementCount() => cast(uint)parent_.idx_.length;
245 
246     /**
247         How many triangles are in the mesh.
248     */
249     @property uint triangleCount() => cast(uint)(parent_.idx_.length / 3);
250 
251     /**
252         Bounds of the deformed mesh.
253     */
254     @property rect bounds() => delta_.getBounds();
255 
256     // Destructor
257     ~this() {
258         nu_freea(vertices_);
259         nu_freea(delta_);
260     }
261 
262     /**
263         Constructs a new DeformedMesh
264     */
265     this(Mesh parent) {
266         this.parent_ = parent;
267 
268         this.vertices_ = nu_malloca!VtxData(parent.points.length);
269         this.delta_ = nu_malloca!vec2(parent.points.length);
270     }
271 
272     /**
273         Constructs a new empty DeformedMesh
274     */
275     this() {
276     }
277 
278     /**
279         Deform the mesh by the given amount.
280 
281         Params:
282             by =        The deltas to deform the mesh by
283     */
284     void deform(vec2[] by) {
285         simd_deform(delta_, by);
286         simd_broadcast_mesh(vertices_, delta_);
287     }
288 
289     /**
290         Deforms the mesh uniformly by the given value.
291 
292         Params:
293             by =        The deltas to deform the mesh by
294     */
295     void deform(vec2 by) {
296         simd_offset(delta_, by);
297         simd_broadcast_mesh(vertices_, delta_);
298     }
299 
300     /**
301         Deforms a single vertex within the mesh by the 
302         given amount.
303 
304         Params:
305             offset =    Offset into the mesh to deform.
306             by =        The delta to deform the mesh by
307     */
308     void deform(size_t offset, vec2 by) {
309         if (offset >= delta_.length)
310             return;
311 
312         delta_[offset] += by;
313         vertices_[offset].vtx.x = delta_[offset].x;
314         vertices_[offset].vtx.y = delta_[offset].y;
315     }
316 
317     /**
318         Pushes a matrix to the deformed mesh.
319     */
320     void pushMatrix(mat4 matrix) {
321         simd_mul(delta_, matrix);
322         simd_broadcast_mesh(vertices_, delta_);
323     }
324 
325     /**
326         Gets an array of every triangle in the mesh.
327 
328         Returns:
329             A nogc array of triangles that you must free
330             yourself with $(D nu_freea).
331     */
332     Triangle[] getTriangles() {
333         Triangle[] tris = nu_malloca!Triangle(triangleCount);
334         foreach (i; 0 .. tris.length) {
335             tris[i] = Triangle(
336                     delta_[parent_.idx_[(i * 3) + 0]].xy,
337                     delta_[parent_.idx_[(i * 3) + 1]].xy,
338                     delta_[parent_.idx_[(i * 3) + 2]].xy
339             );
340         }
341         return tris;
342     }
343 
344     /**
345         Applies an offset to the deformed mesh' UV coordinates.
346 
347         Params:
348             offset =    The offset to apply to the texel coordinates.
349     */
350     void applyUVOffset(vec2 offset) {
351         foreach (ref vtx; vertices_) {
352             vtx.uv += offset;
353         }
354     }
355 
356     /**
357         Resets the deformation.
358     */
359     void reset() {
360         this.vertices_[0 .. $] = parent_.vtx_[0 .. $];
361         this.delta_[0 .. $] = parent_.vto_[0 .. $];
362     }
363 }
364 
365 /**
366     Mesh data as stored in Inochi2D's file format.
367 */
368 struct MeshData {
369 @nogc:
370 
371     /**
372         Vertices in the mesh
373     */
374     vec2[] vertices;
375 
376     /**
377         Base uvs
378     */
379     vec2[] uvs;
380 
381     /**
382         Indices in the mesh
383     */
384     uint[] indices;
385 
386     /**
387         Constructs a new MeshData from slices of mesh data.
388 
389         Params:
390             vertices =  The vertices of the mesh.
391             uvs =       The UV coordinates of the mesh.
392             indices =   The indices of the mesh.
393     */
394     this(vec2[] vertices, vec2[] uvs, uint[] indices) {
395         this.vertices = vertices.nu_dup();
396         this.uvs = uvs.nu_dup();
397         this.indices = indices.nu_dup();
398     }
399 
400     /**
401         Constructs a new MeshData from a refcounted mesh.
402 
403         Params:
404             mesh =  The mesh to extract a MeshData from.
405     */
406     this(Mesh mesh) {
407         this.indices = mesh.indices.nu_dup;
408         this.vertices = nu_malloca!vec2(mesh.vertices.length);
409         this.uvs = nu_malloca!vec2(mesh.vertices.length);
410         foreach (i; 0 .. mesh.vertices.length) {
411             this.vertices[i] = mesh.vertices[i].vtx.xy;
412             this.uvs[i] = mesh.vertices[i].uv;
413         }
414     }
415 
416     /// Serialization handler
417     void onSerialize(ref DataNode object) {
418         object["verts"] = (cast(float[])vertices).serialize();
419         object["uvs"] = (cast(float[])uvs).serialize();
420         object["indices"] = indices.serialize();
421     }
422 
423     /// Deserialization handler
424     void onDeserialize(ref DataNode object, ref ModelState state) {
425         if (object.isNull)
426             return;
427 
428         // Load vertices as tightly packed floats.
429         float[] vtxbuf;
430         object.tryGetRef(state, vtxbuf, "verts");
431         this.vertices = cast(vec2[])vtxbuf[0 .. nu_aligndown(vtxbuf.length, 2)];
432 
433         // Load UVs as tightly packed floats.
434         float[] uvbuf;
435         object.tryGetRef(state, uvbuf, "uvs");
436         this.uvs = cast(vec2[])uvbuf[0 .. nu_aligndown(uvbuf.length, 2)];
437 
438         object.tryGetRef(state, indices, "indices");
439         vec2 origin = object.tryGet!vec2(state, "origin");
440         if (origin.isFinite) {
441             foreach (i; 0 .. vertices.length) {
442                 vertices[i] -= origin;
443             }
444         }
445     }
446 
447     void free() {
448         nu_freea(vertices);
449         nu_freea(uvs);
450         nu_freea(indices);
451     }
452 }
453 
454 /**
455     Calculates bounding box of a mesh.
456 
457     Params:
458         mesh = The mesh to get the bounds for.
459 
460     Returns:
461         A rectangle enclosing the mesh.
462 */
463 rect getBounds(T)(T[] mesh) @nogc nothrow pure
464 if (isVector!T) {
465     vec2 minp = vec2(float.max, float.max);
466     vec2 maxp = vec2(-float.max, -float.max);
467 
468     foreach (i; 0 .. mesh.length) {
469         minp = vec2(min(minp.x, mesh[i].x), min(minp.y, mesh[i].y));
470         maxp = vec2(max(maxp.x, mesh[i].x), max(maxp.y, mesh[i].y));
471     }
472     return rect(minp.x, minp.y, maxp.x - minp.x, maxp.y - minp.y);
473 }