Draw the sun, debug the colour and variable size
[clouds.git] / clouds.cpp
1 #include "debug.hpp"
2 #include "program.hpp"
3 #include "simulation.h"
4 #include <GL/glew.h>
5 #include <GLUT/glut.h>
6 #include <array>
7 #include <cmath>
8 #include <cstdio>
9 #include <cstdlib>
10 #include <glm/ext.hpp>
11 #include <glm/glm.hpp>
12 #include <sys/stat.h>
13 #include <vector>
14
15 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
16
17 enum Mode {
18   render,
19   debugContDist,
20   debugColor,
21   debugProbExt,
22   debugProbAct
23 };
24 Mode curMode = render;
25
26 using namespace std;
27 using namespace glm;
28
29 const float metaballR = 2.f * 1.f / 16.f;
30 inline float metaballField(float r) {
31   if (r > metaballR)
32     return 0;
33   const float a = r / metaballR;
34   return (-4.f / 9.f * powf(a, 6)) + (17.f / 9.f * powf(a, 4)) -
35          (22.f / 9.f * powf(a, 2)) + 1;
36 }
37
38 const float normalizationFactor = 748.f / 405.f * M_PI * metaballR;
39
40 void checkError() {
41   if (GLenum e = glGetError()) {
42     fprintf(stderr, "%s\n", gluErrorString(e));
43     abort();
44   }
45 }
46
47 GLuint bbProg, sunProg;
48 GLuint bbVao;
49
50 // Here we need to generate n_q textures for different densities of metaballs
51 // These textures then go on the billboards
52 // The texture stores attenuation ratio?
53
54 #define NQ 64
55 GLuint bbTexIds[NQ];
56
57 // Stores attenuation ratio inside r channel
58 // Should be highest value at center
59 void precalculateBillboardTextures() {
60   fprintf(stderr, "Calculating billboard textures...\n");
61   glGenTextures(NQ, bbTexIds);
62
63   for (int d = 0; d < NQ; d++) {
64     float data[32 * 32];
65     for (int j = 0; j < 32; j++) {
66       for (int i = 0; i < 32; i++) {
67         // TODO: properly calculate this instead of whatever this is
68         float r = distance(vec2(i, j), vec2(16, 16)) / 32;
69         float density = (float)d / NQ;
70         data[i + j * 32] =
71             1 - (density * 0.01 * metaballField(r * metaballR) / normalizationFactor);
72       }
73     }
74
75     mkdir("bbtex", 0777);
76     char path[32];
77     snprintf(path, 32, "bbtex/%i.tga", d);
78     saveGrayscale(data, 32, 32, path);
79
80     glBindTexture(GL_TEXTURE_2D, bbTexIds[d]);
81     checkError();
82
83     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 32, 32, 0, GL_RED, GL_FLOAT, data);
84     glGenerateMipmap(GL_TEXTURE_2D); // required, otherwise texture is blank
85
86     checkError();
87
88     fprintf(stderr, "\r%i out of %i densities calculated%s", d + 1, NQ,
89             d == NQ - 1 ? "\n" : "");
90   }
91 }
92
93 struct Metaball {
94   vec3 pos;
95   ivec3 coords;
96   /** Density */
97   float d;
98   vec4 col;
99 };
100
101 array<Metaball, CLOUD_DIM_X * CLOUD_DIM_Y * CLOUD_DIM_Z> metaballs;
102
103 Clouds cs;
104
105 void calculateMetaballs() {
106   stepClouds(&cs);
107   /* for (int i = 0; i < 256; i++) { */
108   /*   float x = ((float)rand()/(float)(RAND_MAX) - 0.5) * 2; */
109   /*   float y = ((float)rand()/(float)(RAND_MAX) - 0.5) * 2; */
110   /*   float z = ((float)rand()/(float)(RAND_MAX) - 0.5) * 2; */
111   /*   float r = (float)rand()/(float)(RAND_MAX) * 1; */
112   /*   Metaball m = {{x,y,z}, r}; */
113   /*   metaballs.push_back(m); */
114   /* } */
115   for (int i = 0; i < CLOUD_DIM_X; i++) {
116     for (int j = 0; j < CLOUD_DIM_Y; j++) {
117       for (int k = 0; k < CLOUD_DIM_Z; k++) {
118         const float cloudScale = 1.f / 16;
119         Metaball m = {
120             vec3(i, j, k) * vec3(cloudScale),
121             {i, j, k} };
122         /* m.pos = (m.pos * vec3(2)) - (cloudScale / 2); */
123         m.pos -= vec3(CLOUD_DIM_X, CLOUD_DIM_Y, CLOUD_DIM_Z) * cloudScale / 2.f;
124         m.d = cs.q[i][j][k];
125         metaballs[i * CLOUD_DIM_Y * CLOUD_DIM_Z + j * CLOUD_DIM_Z + k] = m;
126       }
127     }
128   }
129 }
130
131 vec3 sunPos = {0, 5, 5}, sunDir = {0, -1, -1};
132 /* vec4 sunColor = {1,0,0.429,1}; */
133 vec4 sunColor = {1,1,1,1};
134 vec3 camPos = {0, 0, -5}, viewPos = {0, 0, 0};
135 mat4 proj; // projection matrix
136 mat4 view; // view matrix
137 float znear = 0.001, zfar = 1000;
138 float width = 600, height = 400;
139 float aspect = width / height;
140
141 void setProjectionAndViewUniforms(GLuint progId) {
142   GLuint projLoc = glGetUniformLocation(progId, "projection");
143   glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(proj));
144
145   GLuint viewLoc = glGetUniformLocation(progId, "view");
146   glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
147 }
148
149 /** Orientates the transformation matrix to face the camera in the view matrix
150  */
151 mat4 faceView(mat4 m) {
152   m[0][0] = view[0][0];
153   m[0][1] = view[1][0];
154   m[0][2] = view[2][0];
155   m[1][0] = view[0][1];
156   m[1][1] = view[1][1];
157   m[1][2] = view[2][1];
158   m[2][0] = view[0][2];
159   m[2][1] = view[1][2];
160   m[2][2] = view[2][2];
161   return m;
162 }
163
164 GLuint attenuationTex;
165
166 const float metaballScale = metaballR * 1.4f;
167
168 void shadeClouds() {
169   glDisable(GL_DEPTH_TEST);
170   // shaderOutput * 0 + buffer * shader alpha
171   glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
172   glEnable(GL_BLEND);
173
174   // sort by ascending distance from the sun
175   sort(metaballs.begin(), metaballs.end(), [](Metaball &a, Metaball &b) {
176     return distance(sunPos, a.pos) < distance(sunPos, b.pos);
177   });
178
179   glActiveTexture(GL_TEXTURE0);
180   glUniform1i(glGetUniformLocation(bbProg, "tex"), 0);
181
182   GLuint modelLoc = glGetUniformLocation(bbProg, "model");
183   glUniform1i(glGetUniformLocation(bbProg, "debug"), 0);
184
185   for (auto &k : metaballs) {
186     // place the billboard at the center of k
187     mat4 model = translate(mat4(1), k.pos);
188
189     // rotate the billboard so that its normal is oriented to the sun
190     model = faceView(model);
191
192     model = scale(model, vec3(metaballScale));
193
194     glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
195
196     // Set the billboard color as RGBA = (1.0, 1.0, 1.0, 1.0).
197     vec4 color = {1, 1, 1, 1};
198     glUniform4fv(glGetUniformLocation(bbProg, "color"), 1,
199                  glm::value_ptr(color));
200
201     // Map the billboard texture with GL_MODULATE.
202     // i.e. multiply rather than add
203     // but glTexEnv is for the old fixed function pipeline --
204     // need to just tell our fragment shader then to modulate
205     int dIdx = k.d * NQ;
206     glBindTexture(GL_TEXTURE_2D, bbTexIds[dIdx]);
207     glUniform1i(glGetUniformLocation(bbProg, "modulate"), 1);
208
209     // Render the billboard.
210     glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
211
212     // Read the pixel value corresponding to the center of metaball k.
213     // 1. First get position in opengl screen space: from [-1,1]
214     // 2. Normalize to [0,1]
215     // 3. Multiply by (width * height)
216     vec2 screenPos =
217         ((vec2(proj * view * model * vec4(0, 0, 0, 1)) + vec2(1)) / vec2(2)) *
218         vec2(width, height);
219     vec4 pixel;
220     // TODO: This is a huge bottleneck
221     glReadPixels(screenPos.x, screenPos.y, 1, 1, GL_RGBA, GL_FLOAT,
222                  value_ptr(pixel));
223
224     // Multiply the pixel value by the sunlight color.
225     pixel *= sunColor;
226
227     // Store the color into an array C[k] as the color of the billboard.
228     k.col = pixel;
229   }
230
231   saveFBO();
232   checkError();
233 }
234
235 void renderObject() {
236   glDisable(GL_BLEND);
237   // render the sun
238   glUseProgram(sunProg);
239   mat4 model = translate(mat4(1), sunPos);
240   /* model = lookAt(sunPos, sunPos + sunDir, {0, 1, 0}) * model; */
241   model = translate(scale(translate(model, -sunPos), vec3(0.3)), sunPos);
242   glUniformMatrix4fv(glGetUniformLocation(sunProg, "model"), 1, GL_FALSE, glm::value_ptr(model));
243   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 
244 }
245
246 void renderClouds() {
247   glUseProgram(bbProg);
248
249   // Sort metaballs in descending order from the viewpoint
250   sort(metaballs.begin(), metaballs.end(), [](Metaball &a, Metaball &b) {
251     return distance(camPos, a.pos) > distance(camPos, b.pos);
252   });
253
254   glUniform1i(glGetUniformLocation(bbProg, "debug"), curMode != render);
255
256   glDisable(GL_DEPTH_TEST);
257   glEnable(GL_BLEND);
258   // shaderOutput * 1 + buffer * shader alpha
259   glBlendFunc(GL_ONE, GL_SRC_ALPHA);
260
261   glActiveTexture(GL_TEXTURE0);
262   glUniform1i(glGetUniformLocation(bbProg, "tex"), 0);
263
264   for (int i = 0; i < metaballs.size(); i++) {
265     Metaball k = metaballs[i];
266
267     GLuint modelLoc = glGetUniformLocation(bbProg, "model");
268
269     // Place the billboard at the center of the corresponding metaball n.
270     mat4 model = translate(mat4(1), k.pos);
271     // Rotate the billboard so that its normal is oriented to the viewpoint.
272     model = faceView(model);
273
274     model = scale(model, vec3(metaballScale));
275
276     glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
277
278     // Set the billboard color as C[n].
279     k.col.w = 1;
280     glUniform4fv(glGetUniformLocation(bbProg, "color"), 1,
281                  glm::value_ptr(k.col));
282
283     // Map the billboard texture.
284     int dIdx = k.d * NQ;
285     glBindTexture(GL_TEXTURE_2D, bbTexIds[dIdx]);
286
287     // Don't modulate it -- blend it
288     glUniform1i(glGetUniformLocation(bbProg, "modulate"), 0);
289
290     glUniform1f(glGetUniformLocation(bbProg, "debugColor"), curMode == debugColor);
291     if (curMode != render) {
292       float debugVal = 0;
293       if (curMode == debugContDist) debugVal = k.d;
294       else if (curMode == debugProbAct) debugVal = cs.p_act[k.coords.x][k.coords.y][k.coords.z];
295       else if (curMode == debugProbExt) debugVal = cs.p_ext[k.coords.x][k.coords.y][k.coords.z];
296       glUniform1f(glGetUniformLocation(bbProg, "debugVal"), debugVal);
297       glDisable(GL_BLEND);
298       model = scale(model, vec3(0.2));
299       glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
300     }
301
302     // Render the billboard with the blending function.
303     glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
304   }
305 }
306
307 bool needsReshading = true;
308 void display() {
309   if (needsReshading) {
310     // TODO: find a way to make sure there's no clipping
311     view = glm::lookAt(sunPos + sunDir * vec3(20), sunPos, {0, 1, 0});
312     proj = glm::ortho(1.f * aspect, -1.f * aspect, -1.f, 1.f, znear, zfar);
313     setProjectionAndViewUniforms(bbProg);
314
315     glClearColor(1, 1, 1, 1);
316     glClear(GL_COLOR_BUFFER_BIT);
317     shadeClouds();
318     needsReshading = false;
319   }
320
321   view = glm::lookAt(camPos, viewPos, {0, 1, 0});
322   proj = glm::perspective(45.f, aspect, znear, zfar);
323   glUseProgram(sunProg);
324   setProjectionAndViewUniforms(sunProg);
325   glUseProgram(bbProg);
326   setProjectionAndViewUniforms(bbProg);
327
328   glClearColor(0.83, 1, 1, 1); // background color
329   glClear(GL_COLOR_BUFFER_BIT);
330   renderObject(); // render things that aren't clouds
331   renderClouds();
332
333   glutSwapBuffers();
334 }
335
336 bool needsRedisplay = false;
337 void timer(int _) {
338   /* calculateMetaballs(); */
339   if (needsRedisplay) {
340     glutPostRedisplay();
341   }
342   needsRedisplay = false;
343   glutTimerFunc(16, timer, 0);
344 }
345
346 void keyboard(unsigned char key, int x, int y) {
347   if (key == ' ') {
348     calculateMetaballs();
349     needsRedisplay = true;
350     needsReshading = curMode == render;
351   }
352   if (key == '0') {
353     curMode = render;
354     needsRedisplay = true;
355   }
356   if (key == '1') {
357     curMode = debugContDist;
358     needsRedisplay = true;
359   }
360   if (key == '2') {
361     curMode = debugColor;
362     needsRedisplay = true;
363   }
364   if (key == '3') {
365     curMode = debugProbAct;
366     needsRedisplay = true;
367   }
368   if (key == '4') {
369     curMode = debugProbExt;
370     needsRedisplay = true;
371   }
372 }
373
374 int prevMouseX, prevMouseY;
375 bool firstMouse = true;
376 void motion(int x, int y) {
377   if (firstMouse) {
378     prevMouseX = x;
379     prevMouseY = y;
380     firstMouse = false;
381   }
382   float dx = x - prevMouseX, dy = y - prevMouseY;
383   prevMouseX = x;
384   prevMouseY = y;
385   const vec3 origin(0, 0, 0);
386   const float sensitivity = 0.003f;
387   auto camMat = translate(mat4(1), origin + camPos);
388   auto rotation = rotate(rotate(mat4(1), -dx * sensitivity, {0, 1, 0}),
389                          -dy * sensitivity, {1, 0, 0});
390   auto rotAroundOrig = camMat * rotation * translate(mat4(1), origin - camPos);
391   camPos = rotAroundOrig * glm::vec4(camPos, 0);
392   needsRedisplay = true;
393 }
394
395 void passiveMotion(int x, int y) {
396   prevMouseX = x;
397   prevMouseY = y;
398 }
399
400 int main(int argc, char **argv) {
401   glutInit(&argc, argv);
402   glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB |
403                       GLUT_3_2_CORE_PROFILE);
404   glutInitWindowSize(width, height);
405   glutCreateWindow("Clouds");
406   glutDisplayFunc(display);
407
408   glewInit();
409
410   Program prog("billboardvert.glsl", "billboardfrag.glsl");
411   bbProg = prog.progId;
412   Program sProg("sunvert.glsl", "sunfrag.glsl");
413   sunProg = sProg.progId;
414
415   glGenVertexArrays(1, &bbVao);
416   glUseProgram(sunProg);
417   glBindVertexArray(bbVao);
418   glUseProgram(bbProg);
419   glBindVertexArray(bbVao);
420   GLuint vbos[2];
421   glGenBuffers(2, vbos);
422
423   vector<vec3> poss = {{-1, -1, 0}, {-1, 1, 0}, {1, 1, 0}, {1, -1, 0}};
424   vector<GLuint> indices = {2, 1, 0, 3, 2, 0};
425
426   GLuint posLoc = glGetAttribLocation(bbProg, "vPosition");
427   glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
428   glBufferData(GL_ARRAY_BUFFER, poss.size() * sizeof(glm::vec3), &poss[0],
429                GL_STATIC_DRAW);
430   glEnableVertexAttribArray(posLoc);
431   glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
432
433   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
434   glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint),
435                &indices[0], GL_STATIC_DRAW);
436
437   prog.validate();
438
439   precalculateBillboardTextures();
440
441   initClouds(&cs);
442   calculateMetaballs();
443
444   glGenTextures(1, &attenuationTex);
445
446   glutKeyboardFunc(keyboard);
447   glutMotionFunc(motion);
448   glutPassiveMotionFunc(passiveMotion);
449   glutTimerFunc(16, timer, 0);
450
451   // set up billboard prog
452
453   glutMainLoop();
454
455   return 0;
456 }