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 @Optional 41 float[][] gridAxes; 42 43 /** 44 Adds a new vertex 45 */ 46 void add(vec2 vertex, vec2 uv) { 47 vertices ~= vertex; 48 uvs ~= uv; 49 } 50 51 /** 52 Clear connections/indices 53 */ 54 void clearConnections() { 55 indices.length = 0; 56 } 57 58 /** 59 Connects 2 vertices together 60 */ 61 void connect(ushort first, ushort second) { 62 indices ~= [first, second]; 63 } 64 65 /** 66 Find the index of a vertex 67 */ 68 int find(vec2 vert) { 69 foreach(idx, v; vertices) { 70 if (v == vert) return cast(int)idx; 71 } 72 return -1; 73 } 74 75 /** 76 Whether the mesh data is ready to be used 77 */ 78 bool isReady() { 79 return indices.length != 0 && indices.length % 3 == 0; 80 } 81 82 /** 83 Whether the mesh data is ready to be triangulated 84 */ 85 bool canTriangulate() { 86 return indices.length != 0 && indices.length % 3 == 0; 87 } 88 89 /** 90 Fixes the winding order of a mesh. 91 */ 92 void fixWinding() { 93 if (!isReady) return; 94 95 foreach(j; 0..indices.length/3) { 96 size_t i = j*3; 97 98 vec2 vertA = vertices[indices[i+0]]; 99 vec2 vertB = vertices[indices[i+1]]; 100 vec2 vertC = vertices[indices[i+2]]; 101 bool cw = cross(vec3(vertB-vertA, 0), vec3(vertC-vertA, 0)).z < 0; 102 103 // Swap winding 104 if (cw) { 105 ushort swap = indices[i+1]; 106 indices[i+1] = indices[i+2]; 107 indices[i+2] = swap; 108 } 109 } 110 } 111 112 /** 113 Gets connections at a certain point 114 */ 115 int connectionsAtPoint(vec2 point) { 116 int p = find(point); 117 if (p == -1) return 0; 118 return connectionsAtPoint(cast(ushort)p); 119 } 120 121 /** 122 Gets connections at a certain point 123 */ 124 int connectionsAtPoint(ushort point) { 125 int found = 0; 126 foreach(index; indices) { 127 if (index == point) found++; 128 } 129 return found; 130 } 131 132 MeshData copy() { 133 MeshData newData; 134 135 // Copy verts 136 newData.vertices.length = vertices.length; 137 newData.vertices[] = vertices[]; 138 139 // Copy UVs 140 newData.uvs.length = uvs.length; 141 newData.uvs[] = uvs[]; 142 143 // Copy UVs 144 newData.indices.length = indices.length; 145 newData.indices[] = indices[]; 146 147 // Copy axes 148 newData.gridAxes = gridAxes[]; 149 150 newData.origin = vec2(origin.x, origin.y); 151 152 return newData; 153 } 154 155 void serialize(S)(ref S serializer) { 156 auto state = serializer.objectBegin(); 157 serializer.putKey("verts"); 158 auto arr = serializer.arrayBegin(); 159 foreach(vertex; vertices) { 160 serializer.elemBegin; 161 serializer.serializeValue(vertex.x); 162 serializer.elemBegin; 163 serializer.serializeValue(vertex.y); 164 } 165 serializer.arrayEnd(arr); 166 167 if (uvs.length > 0) { 168 serializer.putKey("uvs"); 169 arr = serializer.arrayBegin(); 170 foreach(uv; uvs) { 171 serializer.elemBegin; 172 serializer.serializeValue(uv.x); 173 serializer.elemBegin; 174 serializer.serializeValue(uv.y); 175 } 176 serializer.arrayEnd(arr); 177 } 178 179 serializer.putKey("indices"); 180 serializer.serializeValue(indices); 181 182 serializer.putKey("origin"); 183 origin.serialize(serializer); 184 if (isGrid()) { 185 serializer.putKey("grid_axes"); 186 serializer.serializeValue(gridAxes); 187 } 188 serializer.objectEnd(state); 189 } 190 191 SerdeException deserializeFromFghj(Fghj data) { 192 import std.stdio : writeln; 193 import std.algorithm.searching: count; 194 if (data.isEmpty) return null; 195 196 auto elements = data["verts"].byElement; 197 while(!elements.empty) { 198 float x; 199 float y; 200 elements.front.deserializeValue(x); 201 elements.popFront; 202 elements.front.deserializeValue(y); 203 elements.popFront; 204 vertices ~= vec2(x, y); 205 } 206 207 if (!data["uvs"].isEmpty) { 208 elements = data["uvs"].byElement; 209 while(!elements.empty) { 210 float x; 211 float y; 212 elements.front.deserializeValue(x); 213 elements.popFront; 214 elements.front.deserializeValue(y); 215 elements.popFront; 216 uvs ~= vec2(x, y); 217 } 218 } 219 220 if (!data["origin"].isEmpty) { 221 origin.deserialize(data["origin"]); 222 } 223 224 gridAxes.length = 0; 225 if (!data["grid_axes"].isEmpty) { 226 data["grid_axes"].deserializeValue(gridAxes); 227 } 228 229 foreach(indiceData; data["indices"].byElement) { 230 ushort indice; 231 indiceData.deserializeValue(indice); 232 233 indices ~= indice; 234 } 235 return null; 236 } 237 238 239 /** 240 Generates a quad based mesh which is cut `cuts` amount of times 241 242 vec2i size - size of the mesh 243 uvBounds - x, y UV coordinates + width/height in UV coordinate space 244 cuts - how many time to cut the mesh on the X and Y axis 245 246 Example: 247 Size of Texture Uses all of UV width > height 248 MeshData.createQuadMesh(vec2i(texture.width, texture.height), vec4(0, 0, 1, 1), vec2i(32, 16)) 249 */ 250 static MeshData createQuadMesh(vec2i size, vec4 uvBounds, vec2i cuts = vec2i(6, 6), vec2i origin = vec2i(0)) { 251 252 // Splits may not be below 2. 253 if (cuts.x < 2) cuts.x = 2; 254 if (cuts.y < 2) cuts.y = 2; 255 256 MeshData data; 257 ushort[int[2]] m; 258 int sw = size.x/cuts.x; 259 int sh = size.y/cuts.y; 260 float uvx = uvBounds.w/cast(float)cuts.x; 261 float uvy = uvBounds.z/cast(float)cuts.y; 262 263 // Generate vertices and UVs 264 foreach(y; 0..cuts.y+1) { 265 data.gridAxes[0] ~= y*sh - origin.y; 266 foreach(x; 0..cuts.x+1) { 267 data.gridAxes[1] ~= x*sw - origin.x; 268 data.vertices ~= vec2( 269 (x*sw)-origin.x, 270 (y*sh)-origin.y 271 ); 272 data.uvs ~= vec2( 273 uvBounds.x+cast(float)x*uvx, 274 uvBounds.y+cast(float)y*uvy 275 ); 276 m[[x, y]] = cast(ushort)(data.vertices.length-1); 277 } 278 } 279 280 // Generate indices 281 vec2i center = vec2i(cuts.x/2, cuts.y/2); 282 foreach(y; 0..cuts.y) { 283 foreach(x; 0..cuts.x) { 284 285 // Indices 286 int[2] indice0 = [x, y]; 287 int[2] indice1 = [x, y+1]; 288 int[2] indice2 = [x+1, y]; 289 int[2] indice3 = [x+1, y+1]; 290 291 // We want the verticies to generate in an X pattern so that we won't have too many distortion problems 292 if ((x < center.x && y < center.y) || (x >= center.x && y >= center.y)) { 293 data.indices ~= [ 294 m[indice0], 295 m[indice2], 296 m[indice3], 297 m[indice0], 298 m[indice3], 299 m[indice1], 300 ]; 301 } else { 302 data.indices ~= [ 303 m[indice0], 304 m[indice1], 305 m[indice2], 306 m[indice1], 307 m[indice2], 308 m[indice3], 309 ]; 310 } 311 } 312 } 313 314 return data; 315 } 316 317 bool isGrid() { 318 return gridAxes.length == 2 && gridAxes[0].length > 2 && gridAxes[1].length > 2; 319 } 320 321 bool clearGridIsDirty() { 322 if (gridAxes.length < 2 || gridAxes[0].length == 0 || gridAxes[1].length == 0) 323 return false; 324 325 bool clearGrid() { 326 gridAxes[0].length = 0; 327 gridAxes[1].length = 0; 328 return true; 329 } 330 331 if (vertices.length != gridAxes[0].length * gridAxes[1].length) { 332 return clearGrid(); 333 } 334 335 int index = 0; 336 foreach (y; gridAxes[0]) { 337 foreach (x; gridAxes[1]) { 338 vec2 vert = vec2(x, y); 339 if (vert != vertices[index]) { 340 return clearGrid(); 341 } 342 index += 1; 343 } 344 } 345 return false; 346 } 347 348 bool regenerateGrid() { 349 if (gridAxes[0].length < 2 || gridAxes[1].length < 2) 350 return false; 351 352 vertices.length = 0; 353 uvs.length = 0; 354 indices.length = 0; 355 356 ushort[int[2]] m; 357 358 float minY = gridAxes[0][0], maxY = gridAxes[0][$-1]; 359 float minX = gridAxes[1][0], maxX = gridAxes[1][$-1]; 360 float width = maxY - minY; 361 float height = maxX - minX; 362 foreach (i, y; gridAxes[0]) { 363 foreach (j, x; gridAxes[1]) { 364 vertices ~= vec2(x, y); 365 uvs ~= vec2((x - minX) / width, (y - minY) / height); 366 m[[cast(int)j, cast(int)i]] = cast(ushort)(vertices.length - 1); 367 } 368 } 369 370 vec2 center = vec2(minX + width / 2, minY + height / 2); 371 foreach(i; 0..gridAxes[0].length - 1) { 372 auto yValue = gridAxes[0][i]; 373 foreach(j; 0..gridAxes[1].length - 1) { 374 375 auto xValue = gridAxes[1][j]; 376 int x = cast(int)j, y = cast(int)i; 377 378 // Indices 379 int[2] indice0 = [x , y ]; 380 int[2] indice1 = [x , y+1]; 381 int[2] indice2 = [x+1, y ]; 382 int[2] indice3 = [x+1, y+1]; 383 384 // We want the verticies to generate in an X pattern so that we won't have too many distortion problems 385 if ((xValue < center.x && yValue < center.y) || (xValue >= center.x && yValue >= center.y)) { 386 indices ~= [ 387 m[indice0], 388 m[indice2], 389 m[indice3], 390 m[indice0], 391 m[indice3], 392 m[indice1], 393 ]; 394 } else { 395 indices ~= [ 396 m[indice0], 397 m[indice1], 398 m[indice2], 399 m[indice1], 400 m[indice2], 401 m[indice3], 402 ]; 403 } 404 } 405 } 406 return true; 407 } 408 409 void dbg() { 410 import std.stdio : writefln; 411 writefln("%s %s %s", vertices.length, uvs.length, indices.length); 412 } 413 }