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 }