1 /*
2     Copyright © 2020, Inochi2D Project
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors: Luna Nielsen
6 */
7 module creator.atlas.atlas;
8 import inochi2d;
9 import creator.atlas;
10 import creator.atlas.packer;
11 import bindbc.opengl;
12 
13 private {
14     GLuint writeFBO;
15     GLuint writeVAO;
16     GLuint writeVBO;
17 
18     GLint atlasMVP;
19     Shader atlasShader;
20     Texture currCanvas;
21 
22     void setCanvas(ref Texture canvas) {
23         glBindVertexArray(writeVAO);
24         currCanvas = canvas;
25 
26         glBindFramebuffer(GL_FRAMEBUFFER, writeFBO);
27         glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas.getTextureId(), 0);
28         glBindFramebuffer(GL_FRAMEBUFFER, 0);
29     }
30 
31     void renderToTexture(ref Texture toWrite, rect where, rect uvs) {
32         glBindVertexArray(writeVAO);
33         glBindFramebuffer(GL_FRAMEBUFFER, writeFBO);
34 
35         glViewport(0, 0, currCanvas.width, currCanvas.height);
36         glDisable(GL_CULL_FACE);
37         glDisable(GL_DEPTH_TEST);
38         glEnable(GL_BLEND);
39 
40             glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
41 
42             vec2[] bufData = [
43                 vec2(where.left,    where.top),
44                 vec2(uvs.left,      uvs.top),
45                 vec2(where.left,    where.bottom),
46                 vec2(uvs.left,      uvs.bottom),
47                 vec2(where.right,   where.top),
48                 vec2(uvs.right,     uvs.top),
49                 
50                 vec2(where.right,   where.top),
51                 vec2(uvs.right,     uvs.top),
52                 vec2(where.left,    where.bottom),
53                 vec2(uvs.left,      uvs.bottom),
54                 vec2(where.right,   where.bottom),
55                 vec2(uvs.right,     uvs.bottom),
56             ];
57             glBindBuffer(GL_ARRAY_BUFFER, writeVBO);
58             glBufferData(GL_ARRAY_BUFFER, bufData.length*vec2.sizeof, bufData.ptr, GL_DYNAMIC_DRAW);
59 
60             glEnableVertexAttribArray(0);
61             glEnableVertexAttribArray(1);
62             glVertexAttribPointer(0, 2, GL_FLOAT, false, vec2.sizeof*2, null);
63             glVertexAttribPointer(1, 2, GL_FLOAT, false, vec2.sizeof*2, cast(void*)vec2.sizeof);
64 
65             atlasShader.use();
66             atlasShader.setUniform(atlasMVP, mat4.orthographic(
67                 0, currCanvas.width, currCanvas.height, 0, 0, 2
68             ) * mat4.scaling(1, -1, 1) * mat4.translation(0, -currCanvas.height, -1));
69             toWrite.bind();
70 
71             glDrawArrays(GL_TRIANGLES, 0, 6);
72 
73             glDisableVertexAttribArray(0);
74             glDisableVertexAttribArray(1);
75         glEnable(GL_CULL_FACE);
76         glEnable(GL_DEPTH_TEST);
77         glDisable(GL_BLEND);
78 
79         glBindFramebuffer(GL_FRAMEBUFFER, 0);
80     }
81 }
82 
83 void incInitAtlassing() {
84     glGenFramebuffers(1, &writeFBO);
85     glGenVertexArrays(1, &writeVAO);
86     glGenBuffers(1, &writeVBO);
87 
88     atlasShader = new Shader(import("shaders/atlassing.vert"), import("shaders/atlassing.frag"));
89     atlasMVP = atlasShader.getUniformLocation("mvp");
90 }
91 
92 /**
93     A texture atlas
94 */
95 class Atlas {
96 public:
97     /**
98         The scale of every element
99     */
100     float scale = 1;
101 
102     /**
103         How much padding in pixels to apply
104     */
105     int padding = 4;
106 
107     /**
108         The underlying textures
109     */
110     Texture[TextureUsage.COUNT] textures;
111 
112     /**
113         MaxRects texture packer
114     */
115     TexturePacker packer;
116 
117     /**
118         Mappings from part UUID to an area on the atlas
119     */
120     rect[uint] mappings;
121 
122     /**
123         Constructs a new atlas with the specified size
124     */
125     this(size_t atlasSize, int padding, float scale) {
126         this.padding = padding;
127         this.scale = scale;
128         this.resize(atlasSize);
129     }
130 
131     /**
132         Packs a part in to a atlas, returns whether this was successful
133         atlasArea contains the area in the atlas that the texture was packed in to.
134 
135         UVs should be stretched to cover this area.
136     */
137     bool pack(Part p) {
138         auto mesh = p.getMesh();
139         vec2 textureStartOffset = vec2(0, 0);
140         vec2 textureEndOffset   = vec2(0, 0);
141 
142         // Calculate how much of the texture is actually used in UV coordinates
143         vec4 uvArea = vec4(1, 1, 0, 0);
144         foreach(vec2 uv; mesh.uvs) {
145             if (uv.x < uvArea.x) uvArea.x = uv.x;
146             if (uv.y < uvArea.y) uvArea.y = uv.y;
147             if (uv.x > uvArea.z) uvArea.z = uv.x;
148             if (uv.y > uvArea.w) uvArea.w = uv.y;
149         }
150         if (uvArea.x < 0) {
151             textureStartOffset.x = -uvArea.x;
152             uvArea.x = 0;
153         }
154         if (uvArea.y < 0) {
155             textureStartOffset.y = -uvArea.y;
156             uvArea.y = 0;
157         }
158         if (uvArea.z > 1) {
159             textureEndOffset.x   = uvArea.z - 1;
160             uvArea.z = 1;
161         }
162         if (uvArea.w > 1) {
163             textureEndOffset.y   = uvArea.w - 1;
164             uvArea.w = 1;
165         }
166 
167         // uvRect is always between (0..1)
168         rect uvRect = rect(uvArea.x, uvArea.y, uvArea.z-uvArea.x, uvArea.w-uvArea.y);
169         // boundingUVRect may exceed (0..1) if mesh exceeds the texture boundary.
170         rect boundingUVRect = rect(uvArea.x - textureStartOffset.x, uvArea.y - textureStartOffset.y, 
171                                    uvArea.z - uvArea.x + textureStartOffset.x + textureEndOffset.x, uvArea.w - uvArea.y + textureStartOffset.y + textureEndOffset.y);
172 
173         vec2i size = vec2i(
174             cast(int)((p.textures[0].width*boundingUVRect.width)*scale)+(padding*2), 
175             cast(int)((p.textures[0].height*boundingUVRect.height)*scale)+(padding*2)
176         );
177 
178         // Get a slot for the texture in the atlas
179         vec4 atlasArea = packer.packTexture(size);
180 
181         // Could not fit texture, return false
182         if (atlasArea == vec4i(0, 0, 0, 0)) return false;
183 
184         textureStartOffset.x *= p.textures[0].width  * scale;
185         textureStartOffset.y *= p.textures[0].height * scale;
186         textureEndOffset.x   *= p.textures[0].width  * scale;
187         textureEndOffset.y   *= p.textures[0].height * scale;
188         // Render textures in to our atlas
189         foreach(i, ref Texture texture; p.textures) {
190             if (texture) {
191                 setCanvas(textures[i]);
192                 // where is the calculated texture boundary #2, specifying the area which texture is copied. (alsway between 0..1 in UV position)
193                 rect where = rect(atlasArea.x+textureStartOffset.x+padding, atlasArea.y+textureStartOffset.y+padding, 
194                                   atlasArea.z-(padding*2)-textureStartOffset.x - textureEndOffset.x, atlasArea.w-(padding*2)-textureStartOffset.y - textureEndOffset.y);
195                 mappings[p.uuid] = rect(atlasArea.x+padding, atlasArea.y+padding, atlasArea.z-padding*2, atlasArea.w-padding*2);
196                 renderToTexture(texture, where, uvRect);
197             }
198         }
199         return true;
200     }
201 
202     /**
203         Resizes the atlas
204     */
205     void resize(size_t atlasSize) {
206         packer = new TexturePacker(vec2i(cast(int)atlasSize, cast(int)atlasSize));
207         foreach(i; 0..textures.length) {
208             if (textures[i]) textures[i].dispose();
209 
210             int channels = i == TextureUsage.Albedo ? 4 : 3;
211             textures[i] = new Texture(cast(int)atlasSize, cast(int)atlasSize, channels);
212             // Clear the new texture
213             glBindFramebuffer(GL_FRAMEBUFFER, writeFBO);
214             glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
215                                    textures[i].getTextureId(), 0);
216             glClearColor(0, 0, 0, 0);
217             glClear(GL_COLOR_BUFFER_BIT);
218             glBindFramebuffer(GL_FRAMEBUFFER, 0);
219         }
220     }
221 
222     /**
223         Finalize the atlas
224     */
225     void finalize() {
226         foreach(texture; textures) {
227             texture.genMipmap();
228         }
229     }
230 
231     /**
232         Clears the texture packer
233     */
234     void clear() {
235         mappings.clear();
236         packer.clear();
237     }
238 }