Hack around brightness and shader viewport 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 = 1.f / 16.f;
30 inline float metaballField(float r) {
31   if (r > 1)
32     return 0;
33   const float a = r / (1);
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;
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)) / 16;
69         float density = (float)d / NQ;
70         data[i + j * 32] =
71             1 - (density * 0.7 * (metaballField(r) / 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
82     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 32, 32, 0, GL_RED, GL_FLOAT, data);
83     glGenerateMipmap(GL_TEXTURE_2D); // required, otherwise texture is blank
84
85
86     fprintf(stderr, "\r%i out of %i densities calculated%s", d + 1, NQ,
87             d == NQ - 1 ? "\n" : "");
88   }
89 }
90
91 struct Metaball {
92   vec3 pos;
93   ivec3 coords;
94   /** Density */
95   float d;
96   vec4 col;
97 };
98
99 array<Metaball, CLOUD_DIM_X * CLOUD_DIM_Y * CLOUD_DIM_Z> metaballs;
100
101 const float cloudScale = metaballR;
102 const float metaballScale = metaballR * 3.f;
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         Metaball m = {
119             vec3(i, j, k) * vec3(cloudScale),
120             {i, j, k} };
121         /* m.pos = (m.pos * vec3(2)) - (cloudScale / 2); */
122         m.pos -= vec3(CLOUD_DIM_X, CLOUD_DIM_Y, CLOUD_DIM_Z) * cloudScale / 2.f;
123         m.d = cs.q[i][j][k];
124         /* m.d = 0; */
125         metaballs[i * CLOUD_DIM_Y * CLOUD_DIM_Z + j * CLOUD_DIM_Z + k] = m;
126       }
127     }
128   }
129   /* for (int z = 0; z < CLOUD_DIM_Z; z++) */
130   /*   metaballs[32 * CLOUD_DIM_Y * CLOUD_DIM_Z + 32 * CLOUD_DIM_Z + z].d = 1; */
131 }
132
133 vec3 sunPos = {0, 10, 0}, sunDir = {0, -1, 0};
134 size_t sunColorIdx = 0;
135 std::array<vec4, 2> sunColors = {
136   vec4(1,1,1,1),
137   vec4(0.988,0.309,0.677,1)
138 };
139 vec3 camPos = {0, 0, -5}, viewPos = {0, 0, 0};
140 mat4 proj; // projection matrix
141 mat4 view; // view matrix
142 float znear = 0.001, zfar = 1000;
143 float width = 600, height = 400;
144 float aspect = width / height;
145
146 void setProjectionAndViewUniforms(GLuint progId) {
147   GLuint projLoc = glGetUniformLocation(progId, "projection");
148   glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(proj));
149
150   GLuint viewLoc = glGetUniformLocation(progId, "view");
151   glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
152 }
153
154 /** Orientates the transformation matrix to face the camera in the view matrix
155  */
156 mat4 faceView(mat4 m) {
157   m[0][0] = view[0][0];
158   m[0][1] = view[1][0];
159   m[0][2] = view[2][0];
160   m[1][0] = view[0][1];
161   m[1][1] = view[1][1];
162   m[1][2] = view[2][1];
163   m[2][0] = view[0][2];
164   m[2][1] = view[1][2];
165   m[2][2] = view[2][2];
166   return m;
167 }
168
169 GLuint attenuationTex;
170
171 void shadeClouds() {
172   glDisable(GL_DEPTH_TEST);
173   // shaderOutput * 0 + buffer * shader alpha
174   glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
175   glEnable(GL_BLEND);
176
177   // sort by ascending distance from the sun
178   sort(metaballs.begin(), metaballs.end(), [](Metaball &a, Metaball &b) {
179     return distance(sunPos, a.pos) < distance(sunPos, b.pos);
180   });
181
182   glActiveTexture(GL_TEXTURE0);
183   glUniform1i(glGetUniformLocation(bbProg, "tex"), 0);
184
185   GLuint modelLoc = glGetUniformLocation(bbProg, "model");
186   glUniform1i(glGetUniformLocation(bbProg, "debug"), 0);
187
188   /* GLuint pboBuf; */
189   /* glGenBuffers(1, &pboBuf); */
190   /* glBindBuffer(GL_PIXEL_PACK_BUFFER, pboBuf); */
191
192   /* glViewport(0, 0, shadeWidth, shadeHeight); */
193
194
195   size_t i = 0;
196   for (auto &k : metaballs) {
197     fprintf(stderr, "\rShading cloud %lu/%lu...", i++, metaballs.size());
198     // place the billboard at the center of k
199     mat4 model = translate(mat4(1), k.pos);
200
201     // rotate the billboard so that its normal is oriented to the sun
202     model = faceView(model);
203
204     model = scale(model, vec3(metaballScale));
205
206     glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
207
208     // Set the billboard color as RGBA = (1.0, 1.0, 1.0, 1.0).
209     vec4 color = {1, 1, 1, 1};
210     glUniform4fv(glGetUniformLocation(bbProg, "color"), 1,
211                  glm::value_ptr(color));
212
213     // Map the billboard texture with GL_MODULATE.
214     // i.e. multiply rather than add
215     // but glTexEnv is for the old fixed function pipeline --
216     // need to just tell our fragment shader then to modulate
217     int dIdx = k.d * NQ;
218     glBindTexture(GL_TEXTURE_2D, bbTexIds[dIdx]);
219     glUniform1i(glGetUniformLocation(bbProg, "modulate"), 1);
220
221     // Render the billboard.
222     glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
223
224     // Read the pixel value corresponding to the center of metaball k.
225     // 1. First get position in opengl screen space: from [-1,1]
226     // 2. Normalize to [0,1]
227     // 3. Multiply by (width * height)
228     vec2 screenPos =
229         ((vec2(proj * view * model * vec4(0, 0, 0, 1)) + vec2(1)) / vec2(2)) *
230         vec2(width, height);
231     vec4 pixel;
232     // TODO: This is a huge bottleneck
233     glReadPixels(screenPos.x, screenPos.y, 1, 1, GL_RGBA, GL_FLOAT,
234                  value_ptr(pixel));
235
236     // Multiply the pixel value by the sunlight color.
237     pixel *= sunColors[sunColorIdx % sunColors.size()];
238   
239     // Store the color into an array C[k] as the color of the billboard.
240     k.col = pixel;
241   }
242   fprintf(stderr, "\n");
243
244   saveFBO();
245   checkError();
246   /* glViewport(0, 0, width, height); */
247 }
248
249 void renderObject() {
250   glDisable(GL_BLEND);
251   // render the sun
252   glUseProgram(sunProg);
253   mat4 model = translate(mat4(1), sunPos);
254   /* model = lookAt(sunPos, sunPos + sunDir, {0, 1, 0}) * model; */
255   model = translate(scale(translate(model, -sunPos), vec3(0.3)), sunPos);
256   glUniformMatrix4fv(glGetUniformLocation(sunProg, "model"), 1, GL_FALSE, glm::value_ptr(model));
257   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 
258 }
259
260 void renderClouds() {
261   glUseProgram(bbProg);
262
263   // Sort metaballs in descending order from the viewpoint
264   sort(metaballs.begin(), metaballs.end(), [](Metaball &a, Metaball &b) {
265     return distance(camPos, a.pos) > distance(camPos, b.pos);
266   });
267
268   glUniform1i(glGetUniformLocation(bbProg, "debug"), curMode != render);
269
270   glDisable(GL_DEPTH_TEST);
271   glEnable(GL_BLEND);
272   // shaderOutput * 1 + buffer * shader alpha
273   glBlendFunc(GL_ONE, GL_SRC_ALPHA);
274
275   /* glBlendColor(1.f,1.f,1.f,1.f); */
276   /* glBlendFuncSeparate(GL_ONE, GL_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_SRC_ALPHA); */
277
278   glActiveTexture(GL_TEXTURE0);
279   glUniform1i(glGetUniformLocation(bbProg, "tex"), 0);
280
281   for (int i = 0; i < metaballs.size(); i++) {
282     Metaball k = metaballs[i];
283
284     GLuint modelLoc = glGetUniformLocation(bbProg, "model");
285
286     // Place the billboard at the center of the corresponding metaball n.
287     mat4 model = translate(mat4(1), k.pos);
288     // Rotate the billboard so that its normal is oriented to the viewpoint.
289     model = faceView(model);
290
291     model = scale(model, vec3(metaballScale));
292
293     glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
294
295     // Set the billboard color as C[n].
296     k.col.w = 1;
297     glUniform4fv(glGetUniformLocation(bbProg, "color"), 1,
298                  glm::value_ptr(k.col));
299
300     // Map the billboard texture.
301     int dIdx = k.d * (NQ - 1);
302     glBindTexture(GL_TEXTURE_2D, bbTexIds[dIdx]);
303
304     // Don't modulate it -- blend it
305     glUniform1i(glGetUniformLocation(bbProg, "modulate"), 0);
306
307     glUniform1f(glGetUniformLocation(bbProg, "debugColor"), curMode == debugColor);
308     if (curMode != render) {
309       float debugVal = 0;
310       if (curMode == debugContDist) debugVal = k.d;
311       else if (curMode == debugProbAct) debugVal = cs.p_act[k.coords.x][k.coords.y][k.coords.z];
312       else if (curMode == debugProbExt) debugVal = cs.p_ext[k.coords.x][k.coords.y][k.coords.z];
313       glUniform1f(glGetUniformLocation(bbProg, "debugVal"), debugVal);
314       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
315       model = scale(model, vec3(0.2));
316       glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
317     }
318
319     // Render the billboard with the blending function.
320     glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
321   }
322 }
323
324 bool needsReshading = true;
325 void display() {
326   if (needsReshading) {
327     // TODO: find a way to make sure there's no clipping
328     view = glm::lookAt(sunPos + sunDir * vec3(100.f), sunPos, {0, 0, 1});
329     // TODO: calculate bounds so everything is covered
330     proj = glm::ortho(2.5f, -2.5f, -2.5f, 2.5f, znear, 10000.f);
331     glUseProgram(bbProg);
332     setProjectionAndViewUniforms(bbProg);
333
334     glClearColor(1, 1, 1, 1);
335     glClear(GL_COLOR_BUFFER_BIT);
336     shadeClouds();
337     needsReshading = false;
338   }
339
340   view = glm::lookAt(camPos, viewPos, {0, 1, 0});
341   proj = glm::perspective(45.f, aspect, znear, zfar);
342   glUseProgram(sunProg);
343   setProjectionAndViewUniforms(sunProg);
344   glUseProgram(bbProg);
345   setProjectionAndViewUniforms(bbProg);
346
347   glClearColor(0.83, 1, 1, 1); // background color
348   glClear(GL_COLOR_BUFFER_BIT);
349   renderObject(); // render things that aren't clouds
350   renderClouds();
351
352   glutSwapBuffers();
353 }
354
355 bool needsRedisplay = false;
356 void timer(int _) {
357   /* calculateMetaballs(); */
358   if (needsRedisplay) {
359     glutPostRedisplay();
360   }
361   needsRedisplay = false;
362   glutTimerFunc(16, timer, 0);
363 }
364
365 void keyboard(unsigned char key, int x, int y) {
366   if (key == ' ') {
367     calculateMetaballs();
368     needsRedisplay = true;
369     needsReshading = curMode == render;
370   }
371   if (key == '0') {
372     needsReshading = curMode != render;
373     curMode = render;
374     needsRedisplay = true;
375   }
376   if (key == '1') {
377     curMode = debugContDist;
378     needsRedisplay = true;
379   }
380   if (key == '2') {
381     curMode = debugColor;
382     needsRedisplay = true;
383   }
384   if (key == '3') {
385     curMode = debugProbAct;
386     needsRedisplay = true;
387   }
388   if (key == '4') {
389     curMode = debugProbExt;
390     needsRedisplay = true;
391   }
392   if (key == 's') {
393     sunColorIdx++;
394     needsRedisplay = true;
395     needsReshading = true;
396   }
397 }
398
399 int prevMouseX, prevMouseY;
400 bool firstMouse = true;
401 void motion(int x, int y) {
402   if (firstMouse) {
403     prevMouseX = x;
404     prevMouseY = y;
405     firstMouse = false;
406   }
407   float dx = x - prevMouseX, dy = y - prevMouseY;
408   prevMouseX = x;
409   prevMouseY = y;
410   const vec3 origin(0, 0, 0);
411   const float sensitivity = 0.003f;
412   auto camMat = translate(mat4(1), origin + camPos);
413   auto rotation = rotate(rotate(mat4(1), -dx * sensitivity, {0, 1, 0}),
414                          -dy * sensitivity, {1, 0, 0});
415   auto rotAroundOrig = camMat * rotation * translate(mat4(1), origin - camPos);
416   camPos = rotAroundOrig * glm::vec4(camPos, 0);
417   needsRedisplay = true;
418 }
419
420 void passiveMotion(int x, int y) {
421   prevMouseX = x;
422   prevMouseY = y;
423 }
424
425 int main(int argc, char **argv) {
426   glutInit(&argc, argv);
427   glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB |
428                       GLUT_3_2_CORE_PROFILE);
429   glutInitWindowSize(width, height);
430   glutCreateWindow("Clouds");
431   glutDisplayFunc(display);
432
433   glewInit();
434
435   Program prog("billboardvert.glsl", "billboardfrag.glsl");
436   bbProg = prog.progId;
437   Program sProg("sunvert.glsl", "sunfrag.glsl");
438   sunProg = sProg.progId;
439
440   glGenVertexArrays(1, &bbVao);
441   glUseProgram(sunProg);
442   glBindVertexArray(bbVao);
443   glUseProgram(bbProg);
444   glBindVertexArray(bbVao);
445   GLuint vbos[2];
446   glGenBuffers(2, vbos);
447
448   vector<vec3> poss = {{-1, -1, 0}, {-1, 1, 0}, {1, 1, 0}, {1, -1, 0}};
449   vector<GLuint> indices = {2, 1, 0, 3, 2, 0};
450
451   GLuint posLoc = glGetAttribLocation(bbProg, "vPosition");
452   glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
453   glBufferData(GL_ARRAY_BUFFER, poss.size() * sizeof(glm::vec3), &poss[0],
454                GL_STATIC_DRAW);
455   glEnableVertexAttribArray(posLoc);
456   glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
457
458   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
459   glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint),
460                &indices[0], GL_STATIC_DRAW);
461
462   prog.validate();
463
464   precalculateBillboardTextures();
465
466   initClouds(&cs);
467   calculateMetaballs();
468
469   glGenTextures(1, &attenuationTex);
470
471   glutKeyboardFunc(keyboard);
472   glutMotionFunc(motion);
473   glutPassiveMotionFunc(passiveMotion);
474   glutTimerFunc(16, timer, 0);
475
476   // set up billboard prog
477
478   glutMainLoop();
479
480   return 0;
481 }