1 /*
2 Copyright (c) 2019 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.graphics.cubemap;
29 
30 import std.stdio;
31 import std.math;
32 import std.traits;
33 
34 import dlib.image.color;
35 import dlib.image.image;
36 import dlib.math.vector;
37 import dlib.math.matrix;
38 import dlib.math.transformation;
39 import dlib.math.utils;
40 
41 import dagon.core.libs;
42 import dagon.core.ownership;
43 import dagon.graphics.texture;
44 import dagon.graphics.framebuffer;
45 
46 enum CubeFace
47 {
48     NegativeX = GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
49     PositiveX = GL_TEXTURE_CUBE_MAP_POSITIVE_X,
50     NegativeY = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
51     PositiveY = GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
52     PositiveZ = GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
53     NegativeZ = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
54 }
55 
56 class Cubemap: Texture
57 {
58     this(Owner o)
59     {
60         super(o);
61         initialize();
62     }
63 
64     this(uint resolution, Owner o)
65     {
66         super(o);
67         initialize(resolution);
68     }
69 
70     ~this()
71     {
72         release();
73     }
74 
75     void initialize()
76     {
77         releaseGLTexture();
78 
79         glActiveTexture(GL_TEXTURE0);
80 
81         glGenTextures(1, &tex);
82         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
83 
84         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
85         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
86         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
87         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
88         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
89 
90         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
91     }
92 
93     void initialize(uint resolution)
94     {
95         releaseGLTexture();
96 
97         width = resolution;
98         height = resolution;
99 
100         glActiveTexture(GL_TEXTURE0);
101 
102         glGenTextures(1, &tex);
103         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
104 
105         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
106         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
107         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
108         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
109         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
110 
111         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
112         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
113         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
114         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
115         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
116         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
117 
118         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
119     }
120 
121     void setFaceImage(CubeFace face, SuperImage img)
122     {
123         if (img.width != img.height)
124         {
125             writefln("Cubemap face image must be square");
126             return;
127         }
128 
129         width = img.width;
130         height = img.height;
131 
132         GLenum format;
133         GLint intFormat;
134         GLenum type;
135 
136         if (!pixelFormatToTextureFormat(cast(PixelFormat)img.pixelFormat, format, intFormat, type))
137             writefln("Unsupported pixel format %s", img.pixelFormat);
138 
139         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
140         glTexImage2D(face, 0, intFormat, width, height, 0, format, type, cast(void*)img.data.ptr);
141         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
142     }
143     
144     void fromEquirectangularMap(Texture tex)
145     {
146         fromEquirectangularMap(tex.image);
147     }
148 
149     void fromEquirectangularMap(SuperImage envmap)
150     {
151         SuperImage faceImage = envmap.createSameFormat(width, width);
152 
153         foreach(i, face; EnumMembers!CubeFace)
154         {
155             Matrix4x4f dirTransform = cubeFaceMatrix(face);
156 
157             foreach(x; 0..width)
158             foreach(y; 0..width)
159             {
160                 float cubex = (cast(float)x / cast(float)width) * 2.0f - 1.0f;
161                 float cubey = (1.0f - cast(float)y / cast(float)width) * 2.0f - 1.0f;
162                 Vector3f dir = Vector3f(cubex, cubey, 1.0f).normalized * dirTransform;
163                 Vector2f uv = equirectProj(dir);
164                 Color4f c = bilinearPixel(envmap, uv.x * envmap.width, uv.y * envmap.height);
165                 faceImage[x, y] = c;
166             }
167 
168             setFaceImage(face, faceImage);
169         }
170 
171         faceImage.free();
172     }
173 
174     override void bind()
175     {
176         if (glIsTexture(tex))
177         {
178             glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
179 
180             if (!mipmapGenerated && useMipmapFiltering)
181             {
182                 glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
183                 mipmapGenerated = true;
184             }
185         }
186     }
187 
188     override void unbind()
189     {
190         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
191     }
192 
193     void invalidateMipmap()
194     {
195         mipmapGenerated = false;
196     }
197 }
198 
199 Matrix4x4f cubeFaceMatrix(CubeFace cf)
200 {
201     switch(cf)
202     {
203         case CubeFace.PositiveX:
204             return rotationMatrix(1, degtorad(-90.0f));
205         case CubeFace.NegativeX:
206             return rotationMatrix(1, degtorad(90.0f));
207         case CubeFace.PositiveY:
208             return rotationMatrix(0, degtorad(90.0f));
209         case CubeFace.NegativeY:
210             return rotationMatrix(0, degtorad(-90.0f));
211         case CubeFace.PositiveZ:
212             return rotationMatrix(1, degtorad(0.0f));
213         case CubeFace.NegativeZ:
214             return rotationMatrix(1, degtorad(180.0f));
215         default:
216             return Matrix4x4f.identity;
217     }
218 }
219 
220 Matrix4x4f cubeFaceCameraMatrix(CubeFace cf, Vector3f pos)
221 {
222     Matrix4x4f m;
223     switch(cf)
224     {
225         case CubeFace.PositiveX:
226             m = translationMatrix(pos) * rotationMatrix(1, degtorad(90.0f)) * rotationMatrix(2, degtorad(180.0f));
227             break;
228         case CubeFace.NegativeX:
229             m = translationMatrix(pos) * rotationMatrix(1, degtorad(-90.0f)) * rotationMatrix(2, degtorad(180.0f));
230             break;
231         case CubeFace.PositiveY:
232             m = translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(-90.0f));
233             break;
234         case CubeFace.NegativeY:
235             m = translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(90.0f));
236             break;
237         case CubeFace.PositiveZ:
238             m = translationMatrix(pos) * rotationMatrix(1, degtorad(180.0f)) * rotationMatrix(2, degtorad(180.0f));
239             break;
240         case CubeFace.NegativeZ:
241             m = translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(2, degtorad(180.0f));
242             break;
243         default:
244             m = Matrix4x4f.identity; break;
245     }
246     return m;
247 }
248 
249 Vector2f equirectProj(Vector3f dir)
250 {
251     float phi = acos(dir.y);
252     float theta = atan2(dir.x, dir.z) + PI;
253     return Vector2f(theta / (PI * 2.0f), phi / PI);
254 }