1 /* 2 Inochi2D Part 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.nodes.part; 10 import inochi2d.fmt; 11 import inochi2d.core.nodes.drawable; 12 import inochi2d.core; 13 import inochi2d.math; 14 import bindbc.opengl; 15 import std.exception; 16 import std.algorithm.mutation : copy; 17 public import inochi2d.core.nodes.common; 18 import std.math : isNaN; 19 20 public import inochi2d.core.meshdata; 21 22 23 package(inochi2d) { 24 private { 25 Shader partShader; 26 Shader partMaskShader; 27 28 /* GLSL Uniforms (Normal) */ 29 GLint mvp; 30 GLint offset; 31 GLint gopacity; 32 GLint gtint; 33 34 /* GLSL Uniforms (Masks) */ 35 GLint mmvp; 36 GLint mthreshold; 37 38 GLuint sVertexBuffer; 39 GLuint sUVBuffer; 40 GLuint sElementBuffer; 41 } 42 43 void inInitPart() { 44 inRegisterNodeType!Part; 45 partShader = new Shader(import("basic/basic.vert"), import("basic/basic.frag")); 46 partMaskShader = new Shader(import("basic/basic.vert"), import("basic/basic-mask.frag")); 47 48 mvp = partShader.getUniformLocation("mvp"); 49 offset = partShader.getUniformLocation("offset"); 50 gopacity = partShader.getUniformLocation("opacity"); 51 gtint = partShader.getUniformLocation("tint"); 52 53 mmvp = partMaskShader.getUniformLocation("mvp"); 54 mthreshold = partMaskShader.getUniformLocation("threshold"); 55 56 glGenBuffers(1, &sVertexBuffer); 57 glGenBuffers(1, &sUVBuffer); 58 glGenBuffers(1, &sElementBuffer); 59 } 60 } 61 62 63 /** 64 Creates a simple part that is sized after the texture given 65 part is created based on file path given. 66 Supported file types are: png, tga and jpeg 67 68 This is unoptimal for normal use and should only be used 69 for real-time use when you want to add/remove parts on the fly 70 */ 71 Part inCreateSimplePart(string file, Node parent = null) { 72 return inCreateSimplePart(ShallowTexture(file), parent, file); 73 } 74 75 /** 76 Creates a simple part that is sized after the texture given 77 78 This is unoptimal for normal use and should only be used 79 for real-time use when you want to add/remove parts on the fly 80 */ 81 Part inCreateSimplePart(ShallowTexture texture, Node parent = null, string name = "New Part") { 82 return inCreateSimplePart(new Texture(texture), parent, name); 83 } 84 85 /** 86 Creates a simple part that is sized after the texture given 87 88 This is unoptimal for normal use and should only be used 89 for real-time use when you want to add/remove parts on the fly 90 */ 91 Part inCreateSimplePart(Texture tex, Node parent = null, string name = "New Part") { 92 MeshData data = MeshData([ 93 vec2(-(tex.width/2), -(tex.height/2)), 94 vec2(-(tex.width/2), tex.height/2), 95 vec2(tex.width/2, -(tex.height/2)), 96 vec2(tex.width/2, tex.height/2), 97 ], 98 [ 99 vec2(0, 0), 100 vec2(0, 1), 101 vec2(1, 0), 102 vec2(1, 1), 103 ], 104 [ 105 0, 1, 2, 106 2, 1, 3 107 ]); 108 Part p = new Part(data, [tex], parent); 109 p.name = name; 110 return p; 111 } 112 113 /** 114 Dynamic Mesh Part 115 */ 116 @TypeId("Part") 117 class Part : Drawable { 118 private: 119 120 GLuint uvbo; 121 122 void updateUVs() { 123 glBindBuffer(GL_ARRAY_BUFFER, uvbo); 124 glBufferData(GL_ARRAY_BUFFER, data.uvs.length*vec2.sizeof, data.uvs.ptr, GL_STATIC_DRAW); 125 } 126 127 /* 128 RENDERING 129 */ 130 131 void drawSelf(bool isMask = false)() { 132 133 // In some cases this may happen 134 if (textures.length == 0) return; 135 136 // Bind the vertex array 137 incDrawableBindVAO(); 138 139 static if (isMask) { 140 partMaskShader.use(); 141 partMaskShader.setUniform(offset, data.origin); 142 partMaskShader.setUniform(mmvp, inGetCamera().matrix * transform.matrix()); 143 partMaskShader.setUniform(mthreshold, clamp(offsetMaskThreshold + maskAlphaThreshold, 0, 1)); 144 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 145 } else { 146 partShader.use(); 147 partShader.setUniform(offset, data.origin); 148 partShader.setUniform(mvp, inGetCamera().matrix * transform.matrix()); 149 partShader.setUniform(gopacity, clamp(offsetOpacity * opacity, 0, 1)); 150 151 vec3 clampedColor = tint; 152 if (!offsetTint.x.isNaN) clampedColor.x = clamp(tint.x*offsetTint.x, 0, 1); 153 if (!offsetTint.y.isNaN) clampedColor.y = clamp(tint.y*offsetTint.y, 0, 1); 154 if (!offsetTint.z.isNaN) clampedColor.z = clamp(tint.z*offsetTint.z, 0, 1); 155 partShader.setUniform(gtint, clampedColor); 156 inSetBlendMode(blendingMode); 157 158 // TODO: EXT MODE 159 } 160 161 // Bind the texture 162 textures[0].bind(); 163 164 // Enable points array 165 glEnableVertexAttribArray(0); 166 glBindBuffer(GL_ARRAY_BUFFER, vbo); 167 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 168 169 // Enable UVs array 170 glEnableVertexAttribArray(1); // uvs 171 glBindBuffer(GL_ARRAY_BUFFER, uvbo); 172 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 173 174 // Enable deform array 175 glEnableVertexAttribArray(2); // deforms 176 glBindBuffer(GL_ARRAY_BUFFER, dbo); 177 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, null); 178 179 // Bind index buffer 180 this.bindIndex(); 181 182 // Disable the vertex attribs after use 183 glDisableVertexAttribArray(0); 184 glDisableVertexAttribArray(1); 185 glDisableVertexAttribArray(2); 186 } 187 188 protected: 189 override 190 void renderMask(bool dodge = false) { 191 192 // Enable writing to stencil buffer and disable writing to color buffer 193 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 194 glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 195 glStencilFunc(GL_ALWAYS, dodge ? 0 : 1, 0xFF); 196 glStencilMask(0xFF); 197 198 // Draw ourselves to the stencil buffer 199 drawSelf!true(); 200 201 // Disable writing to stencil buffer and enable writing to color buffer 202 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 203 } 204 205 override 206 string typeId() { return "Part"; } 207 208 /** 209 Allows serializing self data (with pretty serializer) 210 */ 211 override 212 void serializeSelf(ref InochiSerializer serializer) { 213 super.serializeSelf(serializer); 214 215 if (inIsINPMode()) { 216 serializer.putKey("textures"); 217 auto state = serializer.arrayBegin(); 218 foreach(texture; textures) { 219 ptrdiff_t index = puppet.getTextureSlotIndexFor(texture); 220 if (index >= 0) { 221 serializer.elemBegin; 222 serializer.putValue(cast(size_t)index); 223 } 224 } 225 serializer.arrayEnd(state); 226 } else { 227 serializer.putKey("textures"); 228 auto state = serializer.arrayBegin(); 229 serializer.elemBegin; 230 serializer.putValue(name); 231 serializer.arrayEnd(state); 232 } 233 234 serializer.putKey("blend_mode"); 235 serializer.serializeValue(blendingMode); 236 237 serializer.putKey("tint"); 238 tint.serialize(serializer); 239 240 if (masks.length > 0) { 241 serializer.putKey("masks"); 242 auto state = serializer.arrayBegin(); 243 foreach(m; masks) { 244 serializer.elemBegin; 245 serializer.serializeValue(m); 246 } 247 serializer.arrayEnd(state); 248 } 249 250 serializer.putKey("mask_threshold"); 251 serializer.putValue(maskAlphaThreshold); 252 253 serializer.putKey("opacity"); 254 serializer.putValue(opacity); 255 } 256 257 /** 258 Allows serializing self data (with compact serializer) 259 */ 260 override 261 void serializeSelf(ref InochiSerializerCompact serializer) { 262 super.serializeSelf(serializer); 263 264 if (inIsINPMode()) { 265 serializer.putKey("textures"); 266 auto state = serializer.arrayBegin(); 267 foreach(texture; textures) { 268 ptrdiff_t index = puppet.getTextureSlotIndexFor(texture); 269 if (index >= 0) { 270 serializer.elemBegin; 271 serializer.putValue(cast(size_t)index); 272 } 273 } 274 serializer.arrayEnd(state); 275 } else { 276 serializer.putKey("textures"); 277 auto state = serializer.arrayBegin(); 278 serializer.elemBegin; 279 serializer.putValue(name); 280 serializer.arrayEnd(state); 281 } 282 283 serializer.putKey("blend_mode"); 284 serializer.serializeValue(blendingMode); 285 286 serializer.putKey("tint"); 287 tint.serialize(serializer); 288 289 if (masks.length > 0) { 290 serializer.putKey("masks"); 291 auto state = serializer.arrayBegin(); 292 foreach(m; masks) { 293 serializer.elemBegin; 294 serializer.serializeValue(m); 295 } 296 serializer.arrayEnd(state); 297 } 298 299 300 serializer.putKey("mask_threshold"); 301 serializer.putValue(maskAlphaThreshold); 302 303 serializer.putKey("opacity"); 304 serializer.putValue(opacity); 305 306 } 307 308 override 309 SerdeException deserializeFromFghj(Fghj data) { 310 super.deserializeFromFghj(data); 311 312 313 314 if (inIsINPMode()) { 315 316 foreach(texElement; data["textures"].byElement) { 317 uint textureId; 318 texElement.deserializeValue(textureId); 319 this.textures ~= inGetTextureFromId(textureId); 320 } 321 } else { 322 323 // TODO: Index textures by ID 324 string texName; 325 auto elements = data["textures"].byElement; 326 if (!elements.empty) { 327 if (auto exc = elements.front.deserializeValue(texName)) return exc; 328 this.textures = [new Texture(texName)]; 329 } 330 } 331 332 data["opacity"].deserializeValue(this.opacity); 333 data["mask_threshold"].deserializeValue(this.maskAlphaThreshold); 334 335 // Older models may not have tint 336 if (!data["tint"].isEmpty) deserialize(tint, data["tint"]); 337 338 // Older models may not have blend mode 339 if (!data["blend_mode"].isEmpty) data["blend_mode"].deserializeValue(this.blendingMode); 340 341 if (!data["masked_by"].isEmpty) { 342 MaskingMode mode; 343 data["mask_mode"].deserializeValue(mode); 344 345 // Go every masked part 346 foreach(imask; data["masked_by"].byElement) { 347 uint uuid; 348 if (auto exc = imask.deserializeValue(uuid)) return exc; 349 this.masks ~= MaskBinding(uuid, mode, null); 350 } 351 } 352 353 if (!data["masks"].isEmpty) { 354 data["masks"].deserializeValue(this.masks); 355 } 356 357 // Update indices and vertices 358 this.updateUVs(); 359 return null; 360 } 361 362 // 363 // PARAMETER OFFSETS 364 // 365 float offsetMaskThreshold = 0; 366 float offsetOpacity = 1; 367 vec3 offsetTint = vec3(0); 368 369 // TODO: Cache this 370 size_t maskCount() { 371 size_t c; 372 foreach(m; masks) if (m.mode == MaskingMode.Mask) c++; 373 return c; 374 } 375 376 size_t dodgeCount() { 377 size_t c; 378 foreach(m; masks) if (m.mode == MaskingMode.DodgeMask) c++; 379 return c; 380 } 381 382 public: 383 /** 384 List of textures this part can use 385 386 TODO: use more than texture 0 387 */ 388 Texture[] textures; 389 390 /** 391 List of masks to apply 392 */ 393 MaskBinding[] masks; 394 395 /** 396 Blending mode 397 */ 398 BlendMode blendingMode = BlendMode.Normal; 399 400 /** 401 Alpha Threshold for the masking system, the higher the more opaque pixels will be discarded in the masking process 402 */ 403 float maskAlphaThreshold = 0.5; 404 405 /** 406 Opacity of the mesh 407 */ 408 float opacity = 1; 409 410 /** 411 Tint of color based texture 412 */ 413 vec3 tint = vec3(1, 1, 1); 414 415 /** 416 Gets the active texture 417 */ 418 Texture activeTexture() { 419 return textures[0]; 420 } 421 422 /** 423 Constructs a new part 424 */ 425 this(MeshData data, Texture[] textures, Node parent = null) { 426 this(data, textures, inCreateUUID(), parent); 427 } 428 429 /** 430 Constructs a new part 431 */ 432 this(Node parent = null) { 433 super(parent); 434 glGenBuffers(1, &uvbo); 435 } 436 437 /** 438 Constructs a new part 439 */ 440 this(MeshData data, Texture[] textures, uint uuid, Node parent = null) { 441 super(data, uuid, parent); 442 this.textures = textures; 443 glGenBuffers(1, &uvbo); 444 445 mvp = partShader.getUniformLocation("mvp"); 446 gopacity = partShader.getUniformLocation("opacity"); 447 448 mmvp = partMaskShader.getUniformLocation("mvp"); 449 mthreshold = partMaskShader.getUniformLocation("threshold"); 450 this.updateUVs(); 451 } 452 453 override 454 bool hasParam(string key) { 455 if (super.hasParam(key)) return true; 456 457 switch(key) { 458 case "alphaThreshold": 459 case "opacity": 460 case "tint.r": 461 case "tint.g": 462 case "tint.b": 463 return true; 464 default: 465 return false; 466 } 467 } 468 469 override 470 float getDefaultValue(string key) { 471 // Skip our list of our parent already handled it 472 float def = super.getDefaultValue(key); 473 if (!isNaN(def)) return def; 474 475 switch(key) { 476 case "alphaThreshold": 477 return 0; 478 case "opacity": 479 case "tint.r": 480 case "tint.g": 481 case "tint.b": 482 return 1; 483 default: return float(); 484 } 485 } 486 487 override 488 bool setValue(string key, float value) { 489 490 // Skip our list of our parent already handled it 491 if (super.setValue(key, value)) return true; 492 493 switch(key) { 494 case "alphaThreshold": 495 offsetMaskThreshold = value; 496 return true; 497 case "opacity": 498 offsetOpacity = value; 499 return true; 500 case "tint.r": 501 offsetTint.x = value; 502 return true; 503 case "tint.g": 504 offsetTint.y = value; 505 return true; 506 case "tint.b": 507 offsetTint.z = value; 508 return true; 509 default: return false; 510 } 511 } 512 513 bool isMaskedBy(Drawable drawable) { 514 foreach(mask; masks) { 515 if (mask.maskSrc.uuid == drawable.uuid) return true; 516 } 517 return false; 518 } 519 520 ptrdiff_t getMaskIdx(Drawable drawable) { 521 if (drawable is null) return -1; 522 foreach(i, ref mask; masks) { 523 if (mask.maskSrc.uuid == drawable.uuid) return i; 524 } 525 return -1; 526 } 527 528 ptrdiff_t getMaskIdx(uint uuid) { 529 foreach(i, ref mask; masks) { 530 if (mask.maskSrc.uuid == uuid) return i; 531 } 532 return -1; 533 } 534 535 override 536 void beginUpdate() { 537 offsetMaskThreshold = 0; 538 offsetOpacity = 1; 539 offsetTint = vec3(1, 1, 1); 540 super.beginUpdate(); 541 } 542 543 override 544 void rebuffer(ref MeshData data) { 545 super.rebuffer(data); 546 this.updateUVs(); 547 } 548 549 override 550 void draw() { 551 if (!enabled) return; 552 this.drawOne(); 553 554 foreach(child; children) { 555 child.draw(); 556 } 557 } 558 559 override 560 void drawOne() { 561 if (!enabled) return; 562 if (!data.isReady) return; // Yeah, don't even try 563 564 size_t cMasks = maskCount; 565 566 if (masks.length > 0) { 567 import std.stdio : writeln; 568 inBeginMask(cMasks > 0); 569 570 foreach(ref mask; masks) { 571 mask.maskSrc.renderMask(mask.mode == MaskingMode.DodgeMask); 572 } 573 574 inBeginMaskContent(); 575 576 // We are the content 577 this.drawSelf(); 578 579 inEndMask(); 580 return; 581 } 582 583 this.drawSelf(); 584 super.drawOne(); 585 } 586 587 override 588 void drawOneDirect(bool forMasking) { 589 if (forMasking) this.drawSelf!true(); 590 else this.drawSelf!false(); 591 } 592 593 override 594 void finalize() { 595 super.finalize(); 596 foreach(i; 0..masks.length) { 597 if (Drawable nMask = puppet.find!Drawable(masks[i].maskSrcUUID)) { 598 masks[i].maskSrc = nMask; 599 } 600 } 601 } 602 } 603 604 /** 605 Draws a texture at the transform of the specified part 606 */ 607 void inDrawTextureAtPart(Texture texture, Part part) { 608 const float texWidthP = texture.width()/2; 609 const float texHeightP = texture.height()/2; 610 611 // Bind the vertex array 612 incDrawableBindVAO(); 613 614 partShader.use(); 615 partShader.setUniform(mvp, 616 inGetCamera().matrix * 617 mat4.translation(vec3(part.transform.matrix() * vec4(1, 1, 1, 1))) 618 ); 619 partShader.setUniform(gopacity, part.opacity); 620 partShader.setUniform(gtint, part.tint); 621 622 // Bind the texture 623 texture.bind(); 624 625 // Enable points array 626 glEnableVertexAttribArray(0); 627 glBindBuffer(GL_ARRAY_BUFFER, sVertexBuffer); 628 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [ 629 -texWidthP, -texHeightP, 630 texWidthP, -texHeightP, 631 -texWidthP, texHeightP, 632 texWidthP, texHeightP, 633 ].ptr, GL_STATIC_DRAW); 634 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 635 636 // Enable UVs array 637 glEnableVertexAttribArray(1); // uvs 638 glBindBuffer(GL_ARRAY_BUFFER, sUVBuffer); 639 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [ 640 0, 0, 641 1, 0, 642 0, 1, 643 1, 1, 644 ].ptr, GL_STATIC_DRAW); 645 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 646 647 // Bind element array and draw our mesh 648 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sElementBuffer); 649 glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*ushort.sizeof, (cast(ushort[])[ 650 0u, 1u, 2u, 651 2u, 1u, 3u 652 ]).ptr, GL_STATIC_DRAW); 653 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, null); 654 655 // Disable the vertex attribs after use 656 glDisableVertexAttribArray(0); 657 glDisableVertexAttribArray(1); 658 } 659 660 /** 661 Draws a texture at the transform of the specified part 662 */ 663 void inDrawTextureAtPosition(Texture texture, vec2 position, float opacity = 1, vec3 color = vec3(1, 1, 1)) { 664 const float texWidthP = texture.width()/2; 665 const float texHeightP = texture.height()/2; 666 667 // Bind the vertex array 668 incDrawableBindVAO(); 669 670 partShader.use(); 671 partShader.setUniform(mvp, 672 inGetCamera().matrix * 673 mat4.scaling(1, 1, 1) * 674 mat4.translation(vec3(position, 0)) 675 ); 676 partShader.setUniform(gopacity, opacity); 677 partShader.setUniform(gtint, color); 678 679 // Bind the texture 680 texture.bind(); 681 682 // Enable points array 683 glEnableVertexAttribArray(0); 684 glBindBuffer(GL_ARRAY_BUFFER, sVertexBuffer); 685 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [ 686 -texWidthP, -texHeightP, 687 texWidthP, -texHeightP, 688 -texWidthP, texHeightP, 689 texWidthP, texHeightP, 690 ].ptr, GL_STATIC_DRAW); 691 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 692 693 // Enable UVs array 694 glEnableVertexAttribArray(1); // uvs 695 glBindBuffer(GL_ARRAY_BUFFER, sUVBuffer); 696 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, (cast(float[])[ 697 0, 0, 698 1, 0, 699 0, 1, 700 1, 1, 701 ]).ptr, GL_STATIC_DRAW); 702 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 703 704 // Bind element array and draw our mesh 705 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sElementBuffer); 706 glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*ushort.sizeof, (cast(ushort[])[ 707 0u, 1u, 2u, 708 2u, 1u, 3u 709 ]).ptr, GL_STATIC_DRAW); 710 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, null); 711 712 // Disable the vertex attribs after use 713 glDisableVertexAttribArray(0); 714 glDisableVertexAttribArray(1); 715 }