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