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