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.core.ownership;
35 import dlib.image.color;
36 import dlib.image.image;
37 import dlib.math.vector;
38 import dlib.math.matrix;
39 import dlib.math.transformation;
40 import dlib.math.utils;
41 
42 import dagon.core.bindings;
43 import dagon.graphics.texture;
44 
45 enum CubeFace
46 {
47     NegativeX = GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
48     PositiveX = GL_TEXTURE_CUBE_MAP_POSITIVE_X,
49     NegativeY = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
50     PositiveY = GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
51     PositiveZ = GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
52     NegativeZ = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
53 }
54 
55 class Cubemap: Texture
56 {
57     this(Owner o)
58     {
59         super(o);
60         initialize();
61     }
62 
63     this(uint resolution, Owner o)
64     {
65         super(o);
66         initialize(resolution);
67     }
68 
69     ~this()
70     {
71         release();
72     }
73 
74     void initialize()
75     {
76         releaseGLTexture();
77 
78         glActiveTexture(GL_TEXTURE0);
79 
80         glGenTextures(1, &tex);
81         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
82 
83         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
84         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
85         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
86         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
87         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
88 
89         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
90     }
91 
92     void initialize(uint resolution)
93     {
94         releaseGLTexture();
95 
96         width = resolution;
97         height = resolution;
98 
99         glActiveTexture(GL_TEXTURE0);
100 
101         glGenTextures(1, &tex);
102         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
103 
104         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
105         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
106         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
107         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
108         glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
109 
110         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
111         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
112         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
113         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
114         glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
115         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA16F, resolution, resolution, 0, GL_RGBA, GL_FLOAT, null);
116 
117         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
118     }
119 
120     void setFaceImage(CubeFace face, SuperImage img)
121     {
122         if (img.width != img.height)
123         {
124             writefln("Cubemap face image must be square");
125             return;
126         }
127 
128         width = img.width;
129         height = img.height;
130 
131         GLenum format;
132         GLint intFormat;
133         GLenum type;
134 
135         if (!pixelFormatToTextureFormat(cast(PixelFormat)img.pixelFormat, format, intFormat, type))
136             writefln("Unsupported pixel format %s", img.pixelFormat);
137 
138         glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
139         glTexImage2D(face, 0, intFormat, width, height, 0, format, type, cast(void*)img.data.ptr);
140         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
141     }
142     
143     void fromEquirectangularMap(Texture tex)
144     {
145         fromEquirectangularMap(tex.image);
146     }
147 
148     void fromEquirectangularMap(SuperImage envmap)
149     {
150         SuperImage faceImage = envmap.createSameFormat(width, width);
151 
152         foreach(i, face; EnumMembers!CubeFace)
153         {
154             Matrix4x4f dirTransform = cubeFaceMatrix(face);
155 
156             foreach(x; 0..width)
157             foreach(y; 0..width)
158             {
159                 float cubex = (cast(float)x / cast(float)width) * 2.0f - 1.0f;
160                 float cubey = (1.0f - cast(float)y / cast(float)width) * 2.0f - 1.0f;
161                 Vector3f dir = Vector3f(cubex, cubey, 1.0f).normalized * dirTransform;
162                 Vector2f uv = equirectProj(dir);
163                 Color4f c = bilinearPixel(envmap, uv.x * envmap.width, uv.y * envmap.height);
164                 faceImage[x, y] = c;
165             }
166 
167             setFaceImage(face, faceImage);
168         }
169 
170         faceImage.free();
171     }
172 
173     override void bind()
174     {
175         if (glIsTexture(tex))
176         {
177             glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
178 
179             if (!mipmapGenerated && useMipmapFiltering)
180             {
181                 glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
182                 mipmapGenerated = true;
183             }
184         }
185     }
186 
187     override void unbind()
188     {
189         glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
190     }
191 
192     void invalidateMipmap()
193     {
194         mipmapGenerated = false;
195     }
196 }
197 
198 Matrix4x4f cubeFaceMatrix(CubeFace cf)
199 {
200     switch(cf)
201     {
202         case CubeFace.PositiveX:
203             return rotationMatrix(1, degtorad(-90.0f));
204         case CubeFace.NegativeX:
205             return rotationMatrix(1, degtorad(90.0f));
206         case CubeFace.PositiveY:
207             return rotationMatrix(0, degtorad(90.0f));
208         case CubeFace.NegativeY:
209             return rotationMatrix(0, degtorad(-90.0f));
210         case CubeFace.PositiveZ:
211             return rotationMatrix(1, degtorad(0.0f));
212         case CubeFace.NegativeZ:
213             return rotationMatrix(1, degtorad(180.0f));
214         default:
215             return Matrix4x4f.identity;
216     }
217 }
218 
219 Matrix4x4f cubeFaceCameraMatrix(CubeFace cf, Vector3f pos)
220 {
221     Matrix4x4f m;
222     switch(cf)
223     {
224         case CubeFace.PositiveX:
225             m = translationMatrix(pos) * rotationMatrix(1, degtorad(90.0f)) * rotationMatrix(2, degtorad(180.0f));
226             break;
227         case CubeFace.NegativeX:
228             m = translationMatrix(pos) * rotationMatrix(1, degtorad(-90.0f)) * rotationMatrix(2, degtorad(180.0f));
229             break;
230         case CubeFace.PositiveY:
231             m = translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(-90.0f));
232             break;
233         case CubeFace.NegativeY:
234             m = translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(0, degtorad(90.0f));
235             break;
236         case CubeFace.PositiveZ:
237             m = translationMatrix(pos) * rotationMatrix(1, degtorad(180.0f)) * rotationMatrix(2, degtorad(180.0f));
238             break;
239         case CubeFace.NegativeZ:
240             m = translationMatrix(pos) * rotationMatrix(1, degtorad(0.0f)) * rotationMatrix(2, degtorad(180.0f));
241             break;
242         default:
243             m = Matrix4x4f.identity; break;
244     }
245     return m;
246 }
247 
248 Vector2f equirectProj(Vector3f dir)
249 {
250     float phi = acos(dir.y);
251     float theta = atan2(dir.x, dir.z) + PI;
252     return Vector2f(theta / (PI * 2.0f), phi / PI);
253 }