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.integration; 11 import inochi2d.fmt; 12 import inochi2d.core.nodes.drawable; 13 import inochi2d.core; 14 import inochi2d.math; 15 import bindbc.opengl; 16 import std.exception; 17 import std.algorithm.mutation : copy; 18 public import inochi2d.core.nodes.common; 19 import std.math : isNaN; 20 21 public import inochi2d.core.meshdata; 22 23 24 package(inochi2d) { 25 private { 26 Texture boundAlbedo; 27 28 Shader partShader; 29 Shader partShaderStage1; 30 Shader partShaderStage2; 31 Shader partMaskShader; 32 33 /* GLSL Uniforms (Normal) */ 34 GLint mvp; 35 GLint offset; 36 GLint gopacity; 37 GLint gMultColor; 38 GLint gScreenColor; 39 GLint gEmissionStrength; 40 41 42 /* GLSL Uniforms (Stage 1) */ 43 GLint gs1mvp; 44 GLint gs1offset; 45 GLint gs1opacity; 46 GLint gs1MultColor; 47 GLint gs1ScreenColor; 48 49 50 /* GLSL Uniforms (Stage 2) */ 51 GLint gs2mvp; 52 GLint gs2offset; 53 GLint gs2opacity; 54 GLint gs2EmissionStrength; 55 GLint gs2MultColor; 56 GLint gs2ScreenColor; 57 58 /* GLSL Uniforms (Masks) */ 59 GLint mmvp; 60 GLint mthreshold; 61 62 GLuint sVertexBuffer; 63 GLuint sUVBuffer; 64 GLuint sElementBuffer; 65 } 66 67 void inInitPart() { 68 inRegisterNodeType!Part; 69 70 version(InDoesRender) { 71 partShader = new Shader(import("basic/basic.vert"), import("basic/basic.frag")); 72 partShaderStage1 = new Shader(import("basic/basic.vert"), import("basic/basic-stage1.frag")); 73 partShaderStage2 = new Shader(import("basic/basic.vert"), import("basic/basic-stage2.frag")); 74 partMaskShader = new Shader(import("basic/basic.vert"), import("basic/basic-mask.frag")); 75 76 incDrawableBindVAO(); 77 78 partShader.use(); 79 partShader.setUniform(partShader.getUniformLocation("albedo"), 0); 80 partShader.setUniform(partShader.getUniformLocation("emissive"), 1); 81 partShader.setUniform(partShader.getUniformLocation("bumpmap"), 2); 82 mvp = partShader.getUniformLocation("mvp"); 83 offset = partShader.getUniformLocation("offset"); 84 gopacity = partShader.getUniformLocation("opacity"); 85 gMultColor = partShader.getUniformLocation("multColor"); 86 gScreenColor = partShader.getUniformLocation("screenColor"); 87 gEmissionStrength = partShader.getUniformLocation("emissionStrength"); 88 89 partShaderStage1.use(); 90 partShaderStage1.setUniform(partShader.getUniformLocation("albedo"), 0); 91 gs1mvp = partShaderStage1.getUniformLocation("mvp"); 92 gs1offset = partShaderStage1.getUniformLocation("offset"); 93 gs1opacity = partShaderStage1.getUniformLocation("opacity"); 94 gs1MultColor = partShaderStage1.getUniformLocation("multColor"); 95 gs1ScreenColor = partShaderStage1.getUniformLocation("screenColor"); 96 97 partShaderStage2.use(); 98 partShaderStage2.setUniform(partShaderStage2.getUniformLocation("emissive"), 1); 99 partShaderStage2.setUniform(partShaderStage2.getUniformLocation("bumpmap"), 2); 100 gs2mvp = partShaderStage2.getUniformLocation("mvp"); 101 gs2offset = partShaderStage2.getUniformLocation("offset"); 102 gs2opacity = partShaderStage2.getUniformLocation("opacity"); 103 gs2MultColor = partShaderStage2.getUniformLocation("multColor"); 104 gs2ScreenColor = partShaderStage2.getUniformLocation("screenColor"); 105 gs2EmissionStrength = partShaderStage2.getUniformLocation("emissionStrength"); 106 107 partMaskShader.use(); 108 partMaskShader.setUniform(partMaskShader.getUniformLocation("albedo"), 0); 109 partMaskShader.setUniform(partMaskShader.getUniformLocation("emissive"), 1); 110 partMaskShader.setUniform(partMaskShader.getUniformLocation("bumpmap"), 2); 111 mmvp = partMaskShader.getUniformLocation("mvp"); 112 mthreshold = partMaskShader.getUniformLocation("threshold"); 113 114 glGenBuffers(1, &sVertexBuffer); 115 glGenBuffers(1, &sUVBuffer); 116 glGenBuffers(1, &sElementBuffer); 117 } 118 } 119 } 120 121 122 /** 123 Creates a simple part that is sized after the texture given 124 part is created based on file path given. 125 Supported file types are: png, tga and jpeg 126 127 This is unoptimal for normal use and should only be used 128 for real-time use when you want to add/remove parts on the fly 129 */ 130 Part inCreateSimplePart(string file, Node parent = null) { 131 return inCreateSimplePart(ShallowTexture(file), parent, file); 132 } 133 134 /** 135 Creates a simple part that is sized after the texture given 136 137 This is unoptimal for normal use and should only be used 138 for real-time use when you want to add/remove parts on the fly 139 */ 140 Part inCreateSimplePart(ShallowTexture texture, Node parent = null, string name = "New Part") { 141 return inCreateSimplePart(new Texture(texture), parent, name); 142 } 143 144 /** 145 Creates a simple part that is sized after the texture given 146 147 This is unoptimal for normal use and should only be used 148 for real-time use when you want to add/remove parts on the fly 149 */ 150 Part inCreateSimplePart(Texture tex, Node parent = null, string name = "New Part") { 151 MeshData data = MeshData([ 152 vec2(-(tex.width/2), -(tex.height/2)), 153 vec2(-(tex.width/2), tex.height/2), 154 vec2(tex.width/2, -(tex.height/2)), 155 vec2(tex.width/2, tex.height/2), 156 ], 157 [ 158 vec2(0, 0), 159 vec2(0, 1), 160 vec2(1, 0), 161 vec2(1, 1), 162 ], 163 [ 164 0, 1, 2, 165 2, 1, 3 166 ]); 167 Part p = new Part(data, [tex], parent); 168 p.name = name; 169 return p; 170 } 171 172 enum NO_TEXTURE = uint.max; 173 174 enum TextureUsage : size_t { 175 Albedo, 176 Emissive, 177 Bumpmap, 178 COUNT 179 } 180 181 /** 182 Dynamic Mesh Part 183 */ 184 @TypeId("Part") 185 class Part : Drawable { 186 private: 187 GLuint uvbo; 188 189 void updateUVs() { 190 version(InDoesRender) { 191 glBindBuffer(GL_ARRAY_BUFFER, uvbo); 192 glBufferData(GL_ARRAY_BUFFER, data.uvs.length*vec2.sizeof, data.uvs.ptr, GL_STATIC_DRAW); 193 } 194 } 195 196 void setupShaderStage(int stage, mat4 matrix) { 197 198 vec3 clampedTint = tint; 199 if (!offsetTint.x.isNaN) clampedTint.x = clamp(tint.x*offsetTint.x, 0, 1); 200 if (!offsetTint.y.isNaN) clampedTint.y = clamp(tint.y*offsetTint.y, 0, 1); 201 if (!offsetTint.z.isNaN) clampedTint.z = clamp(tint.z*offsetTint.z, 0, 1); 202 203 vec3 clampedScreen = screenTint; 204 if (!offsetScreenTint.x.isNaN) clampedScreen.x = clamp(screenTint.x+offsetScreenTint.x, 0, 1); 205 if (!offsetScreenTint.y.isNaN) clampedScreen.y = clamp(screenTint.y+offsetScreenTint.y, 0, 1); 206 if (!offsetScreenTint.z.isNaN) clampedScreen.z = clamp(screenTint.z+offsetScreenTint.z, 0, 1); 207 208 209 switch(stage) { 210 case 0: 211 // STAGE 1 - Advanced blending 212 213 glDrawBuffers(1, [GL_COLOR_ATTACHMENT0].ptr); 214 215 partShaderStage1.use(); 216 partShaderStage1.setUniform(gs1offset, data.origin); 217 partShaderStage1.setUniform(gs1mvp, inGetCamera().matrix * puppet.transform.matrix * matrix); 218 partShaderStage1.setUniform(gs1opacity, clamp(offsetOpacity * opacity, 0, 1)); 219 220 partShaderStage1.setUniform(partShaderStage1.getUniformLocation("albedo"), 0); 221 partShaderStage1.setUniform(gs1MultColor, clampedTint); 222 partShaderStage1.setUniform(gs1ScreenColor, clampedScreen); 223 inSetBlendMode(blendingMode, false); 224 break; 225 case 1: 226 227 // STAGE 2 - Basic blending (albedo, bump) 228 glDrawBuffers(2, [GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2].ptr); 229 230 partShaderStage2.use(); 231 partShaderStage2.setUniform(gs2offset, data.origin); 232 partShaderStage2.setUniform(gs2mvp, inGetCamera().matrix * puppet.transform.matrix * matrix); 233 partShaderStage2.setUniform(gs2opacity, clamp(offsetOpacity * opacity, 0, 1)); 234 partShaderStage2.setUniform(gs2EmissionStrength, emissionStrength*offsetEmissionStrength); 235 236 partShaderStage2.setUniform(partShaderStage2.getUniformLocation("emission"), 0); 237 partShaderStage2.setUniform(partShaderStage2.getUniformLocation("bump"), 1); 238 239 // These can be reused from stage 2 240 partShaderStage1.setUniform(gs2MultColor, clampedTint); 241 partShaderStage1.setUniform(gs2ScreenColor, clampedScreen); 242 inSetBlendMode(blendingMode, true); 243 break; 244 case 2: 245 246 // Basic blending 247 glDrawBuffers(3, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2].ptr); 248 249 partShader.use(); 250 partShader.setUniform(offset, data.origin); 251 partShader.setUniform(mvp, inGetCamera().matrix * puppet.transform.matrix * matrix); 252 partShader.setUniform(gopacity, clamp(offsetOpacity * opacity, 0, 1)); 253 partShader.setUniform(gEmissionStrength, emissionStrength*offsetEmissionStrength); 254 255 partShader.setUniform(partShader.getUniformLocation("albedo"), 0); 256 partShader.setUniform(partShader.getUniformLocation("emissive"), 1); 257 partShader.setUniform(partShader.getUniformLocation("bumpmap"), 2); 258 259 vec3 clampedColor = tint; 260 if (!offsetTint.x.isNaN) clampedColor.x = clamp(tint.x*offsetTint.x, 0, 1); 261 if (!offsetTint.y.isNaN) clampedColor.y = clamp(tint.y*offsetTint.y, 0, 1); 262 if (!offsetTint.z.isNaN) clampedColor.z = clamp(tint.z*offsetTint.z, 0, 1); 263 partShader.setUniform(gMultColor, clampedColor); 264 265 clampedColor = screenTint; 266 if (!offsetScreenTint.x.isNaN) clampedColor.x = clamp(screenTint.x+offsetScreenTint.x, 0, 1); 267 if (!offsetScreenTint.y.isNaN) clampedColor.y = clamp(screenTint.y+offsetScreenTint.y, 0, 1); 268 if (!offsetScreenTint.z.isNaN) clampedColor.z = clamp(screenTint.z+offsetScreenTint.z, 0, 1); 269 partShader.setUniform(gScreenColor, clampedColor); 270 inSetBlendMode(blendingMode, true); 271 break; 272 default: return; 273 } 274 275 } 276 277 void renderStage(bool advanced=true)(BlendMode mode) { 278 279 // Enable points array 280 glEnableVertexAttribArray(0); 281 glBindBuffer(GL_ARRAY_BUFFER, vbo); 282 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 283 284 // Enable UVs array 285 glEnableVertexAttribArray(1); // uvs 286 glBindBuffer(GL_ARRAY_BUFFER, uvbo); 287 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 288 289 // Enable deform array 290 glEnableVertexAttribArray(2); // deforms 291 glBindBuffer(GL_ARRAY_BUFFER, dbo); 292 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, null); 293 294 // Bind index buffer 295 this.bindIndex(); 296 297 // Disable the vertex attribs after use 298 glDisableVertexAttribArray(0); 299 glDisableVertexAttribArray(1); 300 glDisableVertexAttribArray(2); 301 302 static if (advanced) { 303 // Blending barrier 304 inBlendModeBarrier(mode); 305 } 306 } 307 308 /* 309 RENDERING 310 */ 311 void drawSelf(bool isMask = false)() { 312 313 // In some cases this may happen 314 if (textures.length == 0) return; 315 316 // Bind the vertex array 317 incDrawableBindVAO(); 318 319 // Calculate matrix 320 mat4 matrix = transform.matrix(); 321 if (overrideTransformMatrix !is null) 322 matrix = overrideTransformMatrix.matrix; 323 if (oneTimeTransform !is null) 324 matrix = (*oneTimeTransform) * matrix; 325 326 // Make sure we check whether we're already bound 327 // Otherwise we're wasting GPU resources 328 if (boundAlbedo != textures[0]) { 329 330 // Bind the textures 331 foreach(i, ref texture; textures) { 332 if (texture) texture.bind(cast(uint)i); 333 else { 334 335 // Disable texture when none is there. 336 glActiveTexture(GL_TEXTURE0+cast(uint)i); 337 glBindTexture(GL_TEXTURE_2D, 0); 338 } 339 } 340 } 341 342 static if (isMask) { 343 partMaskShader.use(); 344 partMaskShader.setUniform(offset, data.origin); 345 partMaskShader.setUniform(mmvp, inGetCamera().matrix * puppet.transform.matrix * matrix); 346 partMaskShader.setUniform(mthreshold, clamp(offsetMaskThreshold + maskAlphaThreshold, 0, 1)); 347 348 // Make sure the equation is correct 349 glBlendEquation(GL_FUNC_ADD); 350 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 351 352 renderStage!false(blendingMode); 353 } else { 354 355 bool hasEmissionOrBumpmap = (textures[1] || textures[2]); 356 357 if (inUseMultistageBlending(blendingMode)) { 358 359 // TODO: Detect if this Part is NOT in a composite, 360 // If so, we can relatively safely assume that we may skip stage 1. 361 setupShaderStage(0, matrix); 362 renderStage(blendingMode); 363 364 // Only do stage 2 if we have emission or bumpmap textures. 365 if (hasEmissionOrBumpmap) { 366 setupShaderStage(1, matrix); 367 renderStage!false(blendingMode); 368 } 369 } else { 370 setupShaderStage(2, matrix); 371 renderStage!false(blendingMode); 372 } 373 } 374 375 // Reset draw buffers 376 glDrawBuffers(3, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2].ptr); 377 glBlendEquation(GL_FUNC_ADD); 378 } 379 380 protected: 381 382 override 383 string typeId() { return "Part"; } 384 385 /** 386 Allows serializing self data (with pretty serializer) 387 */ 388 override 389 void serializeSelfImpl(ref InochiSerializer serializer, bool recursive = true) { 390 super.serializeSelfImpl(serializer, recursive); 391 version (InDoesRender) { 392 if (inIsINPMode()) { 393 serializer.putKey("textures"); 394 auto state = serializer.arrayBegin(); 395 foreach(ref texture; textures) { 396 if (texture) { 397 ptrdiff_t index = puppet.getTextureSlotIndexFor(texture); 398 if (index >= 0) { 399 serializer.elemBegin; 400 serializer.putValue(cast(size_t)index); 401 } else { 402 serializer.elemBegin; 403 serializer.putValue(cast(size_t)NO_TEXTURE); 404 } 405 } else { 406 serializer.elemBegin; 407 serializer.putValue(cast(size_t)NO_TEXTURE); 408 } 409 } 410 serializer.arrayEnd(state); 411 } 412 } 413 414 serializer.putKey("blend_mode"); 415 serializer.serializeValue(blendingMode); 416 417 serializer.putKey("tint"); 418 tint.serialize(serializer); 419 420 serializer.putKey("screenTint"); 421 screenTint.serialize(serializer); 422 423 serializer.putKey("emissionStrength"); 424 serializer.serializeValue(emissionStrength); 425 426 if (masks.length > 0) { 427 serializer.putKey("masks"); 428 auto state = serializer.arrayBegin(); 429 foreach(m; masks) { 430 serializer.elemBegin; 431 serializer.serializeValue(m); 432 } 433 serializer.arrayEnd(state); 434 } 435 436 serializer.putKey("mask_threshold"); 437 serializer.putValue(maskAlphaThreshold); 438 439 serializer.putKey("opacity"); 440 serializer.putValue(opacity); 441 } 442 443 override 444 SerdeException deserializeFromFghj(Fghj data) { 445 super.deserializeFromFghj(data); 446 447 448 version(InRenderless) { 449 if (inIsINPMode()) { 450 foreach(texElement; data["textures"].byElement) { 451 uint textureId; 452 texElement.deserializeValue(textureId); 453 if (textureId == NO_TEXTURE) continue; 454 455 textureIds ~= textureId; 456 } 457 } else { 458 assert(0, "Raw Inochi2D JSON not supported in renderless mode"); 459 } 460 461 // Do nothing in this instance 462 } else { 463 if (inIsINPMode()) { 464 465 size_t i; 466 foreach(texElement; data["textures"].byElement) { 467 uint textureId; 468 texElement.deserializeValue(textureId); 469 470 // uint max = no texture set 471 if (textureId == NO_TEXTURE) continue; 472 473 textureIds ~= textureId; 474 this.textures[i++] = inGetTextureFromId(textureId); 475 } 476 } else { 477 enforce(0, "Loading from texture path is deprecated."); 478 } 479 } 480 481 data["opacity"].deserializeValue(this.opacity); 482 data["mask_threshold"].deserializeValue(this.maskAlphaThreshold); 483 484 // Older models may not have tint 485 if (!data["tint"].isEmpty) deserialize(tint, data["tint"]); 486 487 // Older models may not have screen tint 488 if (!data["screenTint"].isEmpty) deserialize(screenTint, data["screenTint"]); 489 490 // Older models may not have emission 491 if (!data["emissionStrength"].isEmpty) deserialize(tint, data["emissionStrength"]); 492 493 // Older models may not have blend mode 494 if (!data["blend_mode"].isEmpty) data["blend_mode"].deserializeValue(this.blendingMode); 495 496 if (!data["masked_by"].isEmpty) { 497 MaskingMode mode; 498 data["mask_mode"].deserializeValue(mode); 499 500 // Go every masked part 501 foreach(imask; data["masked_by"].byElement) { 502 uint uuid; 503 if (auto exc = imask.deserializeValue(uuid)) return exc; 504 this.masks ~= MaskBinding(uuid, mode, null); 505 } 506 } 507 508 if (!data["masks"].isEmpty) { 509 data["masks"].deserializeValue(this.masks); 510 } 511 512 // Update indices and vertices 513 this.updateUVs(); 514 return null; 515 } 516 517 override 518 void serializePartial(ref InochiSerializer serializer, bool recursive=true) { 519 super.serializePartial(serializer, recursive); 520 serializer.putKey("textureUUIDs"); 521 auto state = serializer.arrayBegin(); 522 foreach(ref texture; textures) { 523 uint uuid; 524 if (texture !is null) { 525 uuid = texture.getRuntimeUUID(); 526 } else { 527 uuid = InInvalidUUID; 528 } 529 serializer.elemBegin; 530 serializer.putValue(cast(size_t)uuid); 531 } 532 serializer.arrayEnd(state); 533 } 534 535 // 536 // PARAMETER OFFSETS 537 // 538 float offsetMaskThreshold = 0; 539 float offsetOpacity = 1; 540 float offsetEmissionStrength = 1; 541 vec3 offsetTint = vec3(0); 542 vec3 offsetScreenTint = vec3(0); 543 544 // TODO: Cache this 545 size_t maskCount() { 546 size_t c; 547 foreach(m; masks) if (m.mode == MaskingMode.Mask) c++; 548 return c; 549 } 550 551 size_t dodgeCount() { 552 size_t c; 553 foreach(m; masks) if (m.mode == MaskingMode.DodgeMask) c++; 554 return c; 555 } 556 557 public: 558 /** 559 List of textures this part can use 560 561 TODO: use more than texture 0 562 */ 563 Texture[TextureUsage.COUNT] textures; 564 565 /** 566 List of texture IDs 567 */ 568 int[] textureIds; 569 570 /** 571 List of masks to apply 572 */ 573 MaskBinding[] masks; 574 575 /** 576 Blending mode 577 */ 578 BlendMode blendingMode = BlendMode.Normal; 579 580 /** 581 Alpha Threshold for the masking system, the higher the more opaque pixels will be discarded in the masking process 582 */ 583 float maskAlphaThreshold = 0.5; 584 585 /** 586 Opacity of the mesh 587 */ 588 float opacity = 1; 589 590 /** 591 Strength of emission 592 */ 593 float emissionStrength = 1; 594 595 /** 596 Multiplicative tint color 597 */ 598 vec3 tint = vec3(1, 1, 1); 599 600 /** 601 Screen tint color 602 */ 603 vec3 screenTint = vec3(0, 0, 0); 604 605 /** 606 Gets the active texture 607 */ 608 Texture activeTexture() { 609 return textures[0]; 610 } 611 612 /** 613 Constructs a new part 614 */ 615 this(MeshData data, Texture[] textures, Node parent = null) { 616 this(data, textures, inCreateUUID(), parent); 617 } 618 619 /** 620 Constructs a new part 621 */ 622 this(Node parent = null) { 623 super(parent); 624 625 version(InDoesRender) glGenBuffers(1, &uvbo); 626 } 627 628 /** 629 Constructs a new part 630 */ 631 this(MeshData data, Texture[] textures, uint uuid, Node parent = null) { 632 super(data, uuid, parent); 633 foreach(i; 0..TextureUsage.COUNT) { 634 if (i >= textures.length) break; 635 this.textures[i] = textures[i]; 636 } 637 638 version(InDoesRender) { 639 glGenBuffers(1, &uvbo); 640 641 mvp = partShader.getUniformLocation("mvp"); 642 gopacity = partShader.getUniformLocation("opacity"); 643 644 mmvp = partMaskShader.getUniformLocation("mvp"); 645 mthreshold = partMaskShader.getUniformLocation("threshold"); 646 } 647 648 this.updateUVs(); 649 } 650 651 override 652 void renderMask(bool dodge = false) { 653 654 // Enable writing to stencil buffer and disable writing to color buffer 655 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 656 glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 657 glStencilFunc(GL_ALWAYS, dodge ? 0 : 1, 0xFF); 658 glStencilMask(0xFF); 659 660 // Draw ourselves to the stencil buffer 661 drawSelf!true(); 662 663 // Disable writing to stencil buffer and enable writing to color buffer 664 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 665 } 666 667 override 668 bool hasParam(string key) { 669 if (super.hasParam(key)) return true; 670 671 switch(key) { 672 case "alphaThreshold": 673 case "opacity": 674 case "tint.r": 675 case "tint.g": 676 case "tint.b": 677 case "screenTint.r": 678 case "screenTint.g": 679 case "screenTint.b": 680 case "emissionStrength": 681 return true; 682 default: 683 return false; 684 } 685 } 686 687 override 688 float getDefaultValue(string key) { 689 // Skip our list of our parent already handled it 690 float def = super.getDefaultValue(key); 691 if (!isNaN(def)) return def; 692 693 switch(key) { 694 case "alphaThreshold": 695 return 0; 696 case "opacity": 697 case "tint.r": 698 case "tint.g": 699 case "tint.b": 700 return 1; 701 case "screenTint.r": 702 case "screenTint.g": 703 case "screenTint.b": 704 return 0; 705 case "emissionStrength": 706 return 1; 707 default: return float(); 708 } 709 } 710 711 override 712 bool setValue(string key, float value) { 713 714 // Skip our list of our parent already handled it 715 if (super.setValue(key, value)) return true; 716 717 switch(key) { 718 case "alphaThreshold": 719 offsetMaskThreshold *= value; 720 return true; 721 case "opacity": 722 offsetOpacity *= value; 723 return true; 724 case "tint.r": 725 offsetTint.x *= value; 726 return true; 727 case "tint.g": 728 offsetTint.y *= value; 729 return true; 730 case "tint.b": 731 offsetTint.z *= value; 732 return true; 733 case "screenTint.r": 734 offsetScreenTint.x *= value; 735 return true; 736 case "screenTint.g": 737 offsetScreenTint.y *= value; 738 return true; 739 case "screenTint.b": 740 offsetScreenTint.z *= value; 741 return true; 742 case "emissionStrength": 743 offsetEmissionStrength += value; 744 return true; 745 default: return false; 746 } 747 } 748 749 override 750 float getValue(string key) { 751 switch(key) { 752 case "alphaThreshold": return offsetMaskThreshold; 753 case "opacity": return offsetOpacity; 754 case "tint.r": return offsetTint.x; 755 case "tint.g": return offsetTint.y; 756 case "tint.b": return offsetTint.z; 757 case "screenTint.r": return offsetScreenTint.x; 758 case "screenTint.g": return offsetScreenTint.y; 759 case "screenTint.b": return offsetScreenTint.z; 760 case "emissionStrength": return offsetEmissionStrength; 761 default: return super.getValue(key); 762 } 763 } 764 765 bool isMaskedBy(Drawable drawable) { 766 foreach(mask; masks) { 767 if (mask.maskSrc.uuid == drawable.uuid) return true; 768 } 769 return false; 770 } 771 772 ptrdiff_t getMaskIdx(Drawable drawable) { 773 if (drawable is null) return -1; 774 foreach(i, ref mask; masks) { 775 if (mask.maskSrc.uuid == drawable.uuid) return i; 776 } 777 return -1; 778 } 779 780 ptrdiff_t getMaskIdx(uint uuid) { 781 foreach(i, ref mask; masks) { 782 if (mask.maskSrc.uuid == uuid) return i; 783 } 784 return -1; 785 } 786 787 override 788 void beginUpdate() { 789 offsetMaskThreshold = 0; 790 offsetOpacity = 1; 791 offsetTint = vec3(1, 1, 1); 792 offsetScreenTint = vec3(0, 0, 0); 793 offsetEmissionStrength = 1; 794 super.beginUpdate(); 795 } 796 797 override 798 void rebuffer(ref MeshData data) { 799 super.rebuffer(data); 800 this.updateUVs(); 801 } 802 803 override 804 void draw() { 805 if (!enabled) return; 806 this.drawOne(); 807 808 foreach(child; children) { 809 child.draw(); 810 } 811 } 812 813 override 814 void drawOne() { 815 version (InDoesRender) { 816 if (!enabled) return; 817 if (!data.isReady) return; // Yeah, don't even try 818 819 size_t cMasks = maskCount; 820 821 if (masks.length > 0) { 822 import std.stdio : writeln; 823 inBeginMask(cMasks > 0); 824 825 foreach(ref mask; masks) { 826 mask.maskSrc.renderMask(mask.mode == MaskingMode.DodgeMask); 827 } 828 829 inBeginMaskContent(); 830 831 // We are the content 832 this.drawSelf(); 833 834 inEndMask(); 835 return; 836 } 837 838 // No masks, draw normally 839 this.drawSelf(); 840 } 841 super.drawOne(); 842 } 843 844 override 845 void drawOneDirect(bool forMasking) { 846 if (forMasking) this.drawSelf!true(); 847 else this.drawSelf!false(); 848 } 849 850 override 851 void finalize() { 852 super.finalize(); 853 854 MaskBinding[] validMasks; 855 foreach(i; 0..masks.length) { 856 if (Drawable nMask = puppet.find!Drawable(masks[i].maskSrcUUID)) { 857 masks[i].maskSrc = nMask; 858 validMasks ~= masks[i]; 859 } 860 } 861 862 // Remove invalid masks 863 masks = validMasks; 864 } 865 866 867 override 868 void setOneTimeTransform(mat4* transform) { 869 super.setOneTimeTransform(transform); 870 foreach (m; masks) { 871 m.maskSrc.oneTimeTransform = transform; 872 } 873 } 874 875 } 876 877 /** 878 Draws a texture at the transform of the specified part 879 */ 880 void inDrawTextureAtPart(Texture texture, Part part) { 881 const float texWidthP = texture.width()/2; 882 const float texHeightP = texture.height()/2; 883 884 // Bind the vertex array 885 incDrawableBindVAO(); 886 887 partShader.use(); 888 partShader.setUniform(mvp, 889 inGetCamera().matrix * 890 mat4.translation(vec3(part.transform.matrix() * vec4(1, 1, 1, 1))) 891 ); 892 partShader.setUniform(gopacity, part.opacity); 893 partShader.setUniform(gMultColor, part.tint); 894 partShader.setUniform(gScreenColor, part.screenTint); 895 896 // Bind the texture 897 texture.bind(); 898 899 // Enable points array 900 glEnableVertexAttribArray(0); 901 glBindBuffer(GL_ARRAY_BUFFER, sVertexBuffer); 902 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [ 903 -texWidthP, -texHeightP, 904 texWidthP, -texHeightP, 905 -texWidthP, texHeightP, 906 texWidthP, texHeightP, 907 ].ptr, GL_STATIC_DRAW); 908 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 909 910 // Enable UVs array 911 glEnableVertexAttribArray(1); // uvs 912 glBindBuffer(GL_ARRAY_BUFFER, sUVBuffer); 913 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [ 914 0, 0, 915 1, 0, 916 0, 1, 917 1, 1, 918 ].ptr, GL_STATIC_DRAW); 919 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 920 921 // Bind element array and draw our mesh 922 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sElementBuffer); 923 glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*ushort.sizeof, (cast(ushort[])[ 924 0u, 1u, 2u, 925 2u, 1u, 3u 926 ]).ptr, GL_STATIC_DRAW); 927 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, null); 928 929 // Disable the vertex attribs after use 930 glDisableVertexAttribArray(0); 931 glDisableVertexAttribArray(1); 932 } 933 934 /** 935 Draws a texture at the transform of the specified part 936 */ 937 void inDrawTextureAtPosition(Texture texture, vec2 position, float opacity = 1, vec3 color = vec3(1, 1, 1), vec3 screenColor = vec3(0, 0, 0)) { 938 const float texWidthP = texture.width()/2; 939 const float texHeightP = texture.height()/2; 940 941 // Bind the vertex array 942 incDrawableBindVAO(); 943 944 partShader.use(); 945 partShader.setUniform(mvp, 946 inGetCamera().matrix * 947 mat4.scaling(1, 1, 1) * 948 mat4.translation(vec3(position, 0)) 949 ); 950 partShader.setUniform(gopacity, opacity); 951 partShader.setUniform(gMultColor, color); 952 partShader.setUniform(gScreenColor, screenColor); 953 954 // Bind the texture 955 texture.bind(); 956 957 // Enable points array 958 glEnableVertexAttribArray(0); 959 glBindBuffer(GL_ARRAY_BUFFER, sVertexBuffer); 960 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [ 961 -texWidthP, -texHeightP, 962 texWidthP, -texHeightP, 963 -texWidthP, texHeightP, 964 texWidthP, texHeightP, 965 ].ptr, GL_STATIC_DRAW); 966 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 967 968 // Enable UVs array 969 glEnableVertexAttribArray(1); // uvs 970 glBindBuffer(GL_ARRAY_BUFFER, sUVBuffer); 971 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, (cast(float[])[ 972 0, 0, 973 1, 0, 974 0, 1, 975 1, 1, 976 ]).ptr, GL_STATIC_DRAW); 977 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 978 979 // Bind element array and draw our mesh 980 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sElementBuffer); 981 glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*ushort.sizeof, (cast(ushort[])[ 982 0u, 1u, 2u, 983 2u, 1u, 3u 984 ]).ptr, GL_STATIC_DRAW); 985 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, null); 986 987 // Disable the vertex attribs after use 988 glDisableVertexAttribArray(0); 989 glDisableVertexAttribArray(1); 990 } 991 992 /** 993 Draws a texture at the transform of the specified part 994 */ 995 void inDrawTextureAtRect(Texture texture, rect area, rect uvs = rect(0, 0, 1, 1), float opacity = 1, vec3 color = vec3(1, 1, 1), vec3 screenColor = vec3(0, 0, 0), Shader s = null, Camera cam = null) { 996 997 // Bind the vertex array 998 incDrawableBindVAO(); 999 1000 if (!s) s = partShader; 1001 if (!cam) cam = inGetCamera(); 1002 s.use(); 1003 s.setUniform(s.getUniformLocation("mvp"), 1004 cam.matrix * 1005 mat4.scaling(1, 1, 1) 1006 ); 1007 s.setUniform(s.getUniformLocation("opacity"), opacity); 1008 s.setUniform(s.getUniformLocation("multColor"), color); 1009 s.setUniform(s.getUniformLocation("screenColor"), screenColor); 1010 1011 // Bind the texture 1012 texture.bind(); 1013 1014 // Enable points array 1015 glEnableVertexAttribArray(0); 1016 glBindBuffer(GL_ARRAY_BUFFER, sVertexBuffer); 1017 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, [ 1018 area.left, area.top, 1019 area.right, area.top, 1020 area.left, area.bottom, 1021 area.right, area.bottom, 1022 ].ptr, GL_STATIC_DRAW); 1023 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 1024 1025 // Enable UVs array 1026 glEnableVertexAttribArray(1); // uvs 1027 glBindBuffer(GL_ARRAY_BUFFER, sUVBuffer); 1028 glBufferData(GL_ARRAY_BUFFER, 4*vec2.sizeof, (cast(float[])[ 1029 uvs.x, uvs.y, 1030 uvs.width, uvs.y, 1031 uvs.x, uvs.height, 1032 uvs.width, uvs.height, 1033 ]).ptr, GL_STATIC_DRAW); 1034 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 1035 1036 // Bind element array and draw our mesh 1037 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sElementBuffer); 1038 glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*ushort.sizeof, (cast(ushort[])[ 1039 0u, 1u, 2u, 1040 2u, 1u, 3u 1041 ]).ptr, GL_STATIC_DRAW); 1042 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, null); 1043 1044 // Disable the vertex attribs after use 1045 glDisableVertexAttribArray(0); 1046 glDisableVertexAttribArray(1); 1047 }