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