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 }