1 /*
2     Inochi2D Part Mesh Data
3     
4     Copyright © 2020, Inochi2D Project
5     Distributed under the 2-Clause BSD License, see LICENSE file.
6     
7     Authors: Luna Nielsen
8 */
9 module inochi2d.core.meshdata;
10 import inochi2d.math;
11 import inochi2d.core.texture;
12 import inochi2d.fmt.serialize;
13 
14 /**
15     Mesh data
16 */
17 struct MeshData {
18     /**
19         Vertices in the mesh
20     */
21     vec2[] vertices;
22 
23     /**
24         Base uvs
25     */
26     @Optional
27     vec2[] uvs;
28 
29     /**
30         Indices in the mesh
31     */
32     ushort[] indices;
33 
34     /**
35         Origin of the mesh
36     */
37     @Optional
38     vec2 origin = vec2(0, 0);
39 
40     /**
41         Adds a new vertex
42     */
43     void add(vec2 vertex, vec2 uv) {
44         vertices ~= vertex;
45         uvs ~= uv;
46     }
47 
48     /**
49         Clear connections/indices
50     */
51     void clearConnections() {
52         indices.length = 0;
53     }
54 
55     /**
56         Connects 2 vertices together
57     */
58     void connect(ushort first, ushort second) {
59         indices ~= [first, second];
60     }
61 
62     /**
63         Find the index of a vertex
64     */
65     int find(vec2 vert) {
66         foreach(idx, v; vertices) {
67             if (v == vert) return cast(int)idx;
68         }
69         return -1;
70     }
71 
72     /**
73         Whether the mesh data is ready to be used
74     */
75     bool isReady() {
76         return indices.length != 0 && indices.length % 3 == 0;
77     }
78 
79     /**
80         Whether the mesh data is ready to be triangulated
81     */
82     bool canTriangulate() {
83         return indices.length != 0 && indices.length % 3 == 0;
84     }
85 
86     /**
87         Fixes the winding order of a mesh.
88     */
89     void fixWinding() {
90         if (!isReady) return;
91         
92         foreach(j; 0..indices.length/3) {
93             size_t i = j*3;
94 
95             vec2 vertA = vertices[indices[i+0]];
96             vec2 vertB = vertices[indices[i+1]];
97             vec2 vertC = vertices[indices[i+2]];
98             bool cw = cross(vec3(vertB-vertA, 0), vec3(vertC-vertA, 0)).z < 0;
99 
100             // Swap winding
101             if (cw) {
102                 ushort swap = indices[i+1];
103                 indices[i+1] = indices[i+2];
104                 indices[i+2] = swap;   
105             }
106         }
107     }
108 
109     /**
110         Gets connections at a certain point
111     */
112     int connectionsAtPoint(vec2 point) {
113         int p = find(point);
114         if (p == -1) return 0;
115         return connectionsAtPoint(cast(ushort)p);
116     }
117 
118     /**
119         Gets connections at a certain point
120     */
121     int connectionsAtPoint(ushort point) {
122         int found = 0;
123         foreach(index; indices) {
124             if (index == point) found++;
125         }
126         return found;
127     }
128 
129     MeshData copy() {
130         MeshData newData;
131 
132         // Copy verts
133         newData.vertices.length = vertices.length;
134         newData.vertices[] = vertices[];
135 
136         // Copy UVs
137         newData.uvs.length = uvs.length;
138         newData.uvs[] = uvs[];
139 
140         // Copy UVs
141         newData.indices.length = indices.length;
142         newData.indices[] = indices[];
143 
144         newData.origin = vec2(origin.x, origin.y);
145 
146         return newData;
147     }
148 
149     void serialize(S)(ref S serializer) {
150         auto state = serializer.objectBegin();
151             serializer.putKey("verts");
152             auto arr = serializer.arrayBegin();
153                 foreach(vertex; vertices) {
154                     serializer.elemBegin;
155                     serializer.serializeValue(vertex.x);
156                     serializer.elemBegin;
157                     serializer.serializeValue(vertex.y);
158                 }
159             serializer.arrayEnd(arr);
160 
161             if (uvs.length > 0) {
162                 serializer.putKey("uvs");
163                 arr = serializer.arrayBegin();
164                     foreach(uv; uvs) {
165                         serializer.elemBegin;
166                         serializer.serializeValue(uv.x);
167                         serializer.elemBegin;
168                         serializer.serializeValue(uv.y);
169                     }
170                 serializer.arrayEnd(arr);
171             }
172 
173             serializer.putKey("indices");
174             serializer.serializeValue(indices);
175 
176             serializer.putKey("origin");
177             origin.serialize(serializer);
178         serializer.objectEnd(state);
179     }
180 
181     SerdeException deserializeFromFghj(Fghj data) {
182         import std.stdio : writeln;
183         import std.algorithm.searching: count;
184         if (data.isEmpty) return null;
185 
186         auto elements = data["verts"].byElement;
187         while(!elements.empty) {
188             float x;
189             float y;
190             elements.front.deserializeValue(x);
191             elements.popFront;
192             elements.front.deserializeValue(y);
193             elements.popFront;
194             vertices ~= vec2(x, y);
195         }
196 
197         if (!data["uvs"].isEmpty) {
198             elements = data["uvs"].byElement;
199             while(!elements.empty) {
200                 float x;
201                 float y;
202                 elements.front.deserializeValue(x);
203                 elements.popFront;
204                 elements.front.deserializeValue(y);
205                 elements.popFront;
206                 uvs ~= vec2(x, y);
207             }
208         }
209 
210         if (!data["origin"].isEmpty) {
211             origin.deserialize(data["origin"]);
212         }
213 
214         foreach(indiceData; data["indices"].byElement) {
215             ushort indice;
216             indiceData.deserializeValue(indice);
217             
218             indices ~= indice;
219         }
220         return null;
221     }
222 
223 
224     /**
225         Generates a quad based mesh which is cut `cuts` amount of times
226 
227         vec2i size - size of the mesh
228         uvBounds - x, y UV coordinates + width/height in UV coordinate space
229         cuts - how many time to cut the mesh on the X and Y axis
230 
231         Example:
232                                 Size of Texture                       Uses all of UV    width > height
233         MeshData.createQuadMesh(vec2i(texture.width, texture.height), vec4(0, 0, 1, 1), vec2i(32, 16))
234     */
235     static MeshData createQuadMesh(vec2i size, vec4 uvBounds, vec2i cuts = vec2i(6, 6), vec2i origin = vec2i(0)) {
236         
237         // Splits may not be below 2.
238         if (cuts.x < 2) cuts.x = 2;
239         if (cuts.y < 2) cuts.y = 2;
240 
241         MeshData data;
242         ushort[int[2]] m;
243         int sw = size.x/cuts.x;
244         int sh = size.y/cuts.y;
245         float uvx = uvBounds.w/cast(float)cuts.x;
246         float uvy = uvBounds.z/cast(float)cuts.y;
247 
248         // Generate vertices and UVs
249         foreach(y; 0..cuts.y+1) {
250             foreach(x; 0..cuts.x+1) {
251                 data.vertices ~= vec2(
252                     (x*sw)-origin.x, 
253                     (y*sh)-origin.y
254                 );
255                 data.uvs ~= vec2(
256                     uvBounds.x+cast(float)x*uvx, 
257                     uvBounds.y+cast(float)y*uvy
258                 );
259                 m[[x, y]] = cast(ushort)(data.vertices.length-1); 
260             }	
261         }
262 
263         // Generate indices
264         vec2i center = vec2i(cuts.x/2, cuts.y/2);
265         foreach(y; 0..cuts.y) {
266             foreach(x; 0..cuts.x) {
267 
268                 // Indices
269                 int[2] indice0 = [x, y];
270                 int[2] indice1 = [x, y+1];
271                 int[2] indice2 = [x+1, y];
272                 int[2] indice3 = [x+1, y+1];
273 
274                 // We want the verticies to generate in an X pattern so that we won't have too many distortion problems
275                 if ((x < center.x && y < center.y) || (x >= center.x && y >= center.y)) {
276                     data.indices ~= [
277                         m[indice0],
278                         m[indice2],
279                         m[indice3],
280                         m[indice0],
281                         m[indice3],
282                         m[indice1],
283                     ];
284                 } else {
285                     data.indices ~= [
286                         m[indice0],
287                         m[indice1],
288                         m[indice2],
289                         m[indice1],
290                         m[indice2],
291                         m[indice3],
292                     ];
293                 }
294             }
295         }
296 
297 
298         return data;
299     }
300 
301     void dbg() {
302         import std.stdio : writefln;
303         writefln("%s %s %s", vertices.length, uvs.length, indices.length);
304     }
305 }