OpenClonk
C4FoWDrawStrategy.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2014-2016, The OpenClonk Team and contributors
5  *
6  * Distributed under the terms of the ISC license; see accompanying file
7  * "COPYING" for details.
8  *
9  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10  * See accompanying file "TRADEMARK" for details.
11  *
12  * To redistribute this file separately, substitute the full license texts
13  * for the above references.
14  */
15 
16 #include "C4Include.h"
18 
19 #ifndef USE_CONSOLE
20 
24 #include "graphics/C4DrawGL.h"
25 
27 
29 {
30  FinishPrimitive();
31  mode = M_Fan;
32 }
33 
35 {
36  FinishPrimitive();
37  mode = M_Quads;
38 }
39 
40 void C4FoWDrawTriangulator::FinishPrimitive()
41 {
42  // Check the current primitive is complete
43  switch (mode)
44  {
45  case M_Fan:
46  assert(cur_vertices == 0 || cur_vertices >= 3);
47  break;
48  case M_Quads:
49  assert(cur_vertices % 4 == 0);
50  break;
51  default:
52  assert(false);
53  break;
54  }
55 
56  // Reset primitve counter
57  begin_vertices += cur_vertices;
58  cur_vertices = 0;
59 }
60 
62 {
63  switch (mode)
64  {
65  case M_Fan:
66  ++cur_vertices;
67  if (cur_vertices == 3)
68  {
69  indices.push_back(begin_vertices);
70  indices.push_back(begin_vertices + 1);
71  indices.push_back(begin_vertices + 2);
72  }
73  else if (cur_vertices > 3)
74  {
75  indices.push_back(begin_vertices);
76  indices.push_back(begin_vertices + cur_vertices - 2);
77  indices.push_back(begin_vertices + cur_vertices - 1);
78  }
79  break;
80  case M_Quads:
81  ++cur_vertices;
82  if (cur_vertices % 4 == 0)
83  {
84  // upper tri
85  indices.push_back(begin_vertices + cur_vertices - 4);
86  indices.push_back(begin_vertices + cur_vertices - 3);
87  indices.push_back(begin_vertices + cur_vertices - 2);
88 
89  // lower tri
90  indices.push_back(begin_vertices + cur_vertices - 4);
91  indices.push_back(begin_vertices + cur_vertices - 2);
92  indices.push_back(begin_vertices + cur_vertices - 1);
93  }
94  break;
95  default:
96  assert(false);
97  }
98 }
99 
101 {
102  FinishPrimitive();
103  begin_vertices = 0;
104 
105  // Assume this keeps capacity
106  indices.resize(0);
107 }
108 
110  : light(light), region(nullptr), vbo_size(0), ibo_size(0)
111 {
112  bo[0] = bo[1] = 0u;
113 }
114 
116 {
117  if (bo[0])
118  {
119  glDeleteBuffers(2, bo);
120  pGL->FreeVAOID(vaoids[2]);
121  pGL->FreeVAOID(vaoids[1]);
122  pGL->FreeVAOID(vaoids[0]);
123  }
124 }
125 
127 {
128  region = regionPar;
129  if (!bo[0])
130  {
131  // lazy-init buffer objects
132  glGenBuffers(2, bo);
133  vaoids[0] = pGL->GenVAOID();
134  vaoids[1] = pGL->GenVAOID();
135  vaoids[2] = pGL->GenVAOID();
136  }
137 }
138 
140 {
141  // If we have nothing to draw (e.g. directly after initialization), abort early.
142  if (vertices.empty()) return;
143 
144  const GLuint vbo = bo[0];
145  const GLuint ibo = bo[1];
146 
147  // Upload vertices
148  glBindBuffer(GL_ARRAY_BUFFER, vbo);
149  if (vbo_size < vertices.size())
150  {
151  glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_DYNAMIC_DRAW);
152  vbo_size = vertices.size();
153  }
154  else
155  {
156  glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(Vertex), &vertices[0]);
157  }
158  glBindBuffer(GL_ARRAY_BUFFER, 0);
159 
160  // Upload indices
161  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
162  if (ibo_size < triangulator.GetNIndices())
163  {
164  glBufferData(GL_ELEMENT_ARRAY_BUFFER, triangulator.GetNIndices() * sizeof(GLuint), triangulator.GetIndices(), GL_DYNAMIC_DRAW);
165  ibo_size = triangulator.GetNIndices();
166  }
167  else
168  {
169  glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, triangulator.GetNIndices() * sizeof(GLuint), triangulator.GetIndices());
170  }
171  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
172 
173  // Region dimensions
174  const float width = region->getSurfaceWidth();
175  const float height = region->getSurfaceHeight() / 2.0;
176 
177  // Set Y offset for vertex
178  float y_offset[] = { 0.0f, 0.0f };
179  call.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset);
180 
181  // Enable scissor test to only draw in upper or lower half of texture
182  glEnable(GL_SCISSOR_TEST);
183  glScissor(0, height, width, height);
184 
185  // Setup state for 1st pass
186  GLuint vao1, vao2, vao3;
187  const bool has_vao1 = pGL->GetVAO(vaoids[0], vao1);
188  glBindVertexArray(vao1);
189  if (!has_vao1)
190  {
191  glBindBuffer(GL_ARRAY_BUFFER, vbo);
192  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
193  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Position));
194  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Color));
195  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, x)));
196  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, r1)));
197  }
198 
199  // Set up blend equation, see C4FoWDrawLightTextureStrategy::DrawVertex
200  // for details.
201  glBlendFunc(GL_ONE, GL_ONE);
202  glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX);
203 
204  // Render 1st pass
205  glDrawElements(GL_TRIANGLES, triangulator.GetNIndices(), GL_UNSIGNED_INT, nullptr);
206 
207  // Prepare state for 2nd pass
208  //glBlendFunc(GL_ONE, GL_ONE);
209  glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
210 
211  const bool has_vao2 = pGL->GetVAO(vaoids[1], vao2);
212  glBindVertexArray(vao2);
213  if (!has_vao2)
214  {
215  glBindBuffer(GL_ARRAY_BUFFER, vbo);
216  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
217  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Position));
218  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Color));
219  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, x)));
220  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, r2)));
221  }
222 
223  // Render 2nd pass
224  glDrawElements(GL_TRIANGLES, triangulator.GetNIndices(), GL_UNSIGNED_INT, nullptr);
225 
226  // Prepare state for 3rd pass (color pass)
227  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
228  glBlendEquation(GL_FUNC_ADD);
229  glScissor(0, 0, width, height);
230  y_offset[1] = height;
231  call.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset);
232 
233  const bool has_vao3 = pGL->GetVAO(vaoids[2], vao3);
234  glBindVertexArray(vao3);
235  if (!has_vao3)
236  {
237  glBindBuffer(GL_ARRAY_BUFFER, vbo);
238  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
239  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Position));
240  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Color));
241  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, x)));
242  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, r3)));
243  }
244 
245  // Render 3rd pass
246  glDrawElements(GL_TRIANGLES, triangulator.GetNIndices(), GL_UNSIGNED_INT, nullptr);
247 
248  // Reset GL state
249  glBindVertexArray(0);
250  glDisable(GL_SCISSOR_TEST);
251 
252  // Assume the capacity stays the same:
253  vertices.resize(0);
255 }
256 
257 void C4FoWDrawLightTextureStrategy::DrawVertex(float x, float y, bool shadow)
258 {
259 
260  // Here's the master plan for updating the lights texture. We want to
261  //
262  // 1. sum up the normals, weighted by intensity
263  // 2. take over intensity maximum as new intensity
264  //
265  // where intensity is in the A channel and normals are in the GB channels.
266  // Normals are obviously meant to be though of as signed, though,
267  // so the equation we want would be something like
268  //
269  // A_new = max(A_old, A);
270  // G_new = BoundBy(G_old + G - 0.5, 0.0, 1.0);
271  // B_new = BoundBy(B_old + B - 0.5, 0.0, 1.0);
272  //
273  // It seems we can't get that directly though - glBlendFunc only talks
274  // about two operands. Even if we make two passes, we have to take
275  // care that that we don't over- or underflow in the intermediate pass.
276  //
277  // Therefore, we store G/1.5 instead of G, losing a bit of accuracy,
278  // but allowing us to formulate the following approximation without
279  // overflows:
280  //
281  // G_new = BoundBy(BoundBy(G_old + G / 1.5), 0.0, 1.0) - 0.5 / 1.5, 0.0, 1.0)
282  // B_new = BoundBy(BoundBy(B_old + B / 1.5), 0.0, 1.0) - 0.5 / 1.5, 0.0, 1.0)
283 
284  vertices.emplace_back();
285  Vertex& vtx = vertices.back();
286  vtx.x = x; vtx.y = y;
287 
288  // global coords -> region coords
289  // TODO: We could also do this in the shader...
290  vtx.x += -region->getRegion().x;
291  vtx.y += -region->getRegion().y;
292 
293  // First pass color
294  if (shadow)
295  {
296  float dx = x - light->getX();
297  float dy = y - light->getY();
298  float dist = sqrt(dx*dx+dy*dy);
299  float bright = light->getBrightness();
300  float mult = std::min(0.5f / light->getNormalSize(), 0.5f / dist);
301  float normX = Clamp(0.5f + dx * mult, 0.0f, 1.0f) / 1.5f;
302  float normY = Clamp(0.5f + dy * mult, 0.0f, 1.0f) / 1.5f;
303  vtx.r1 = 0.0f; vtx.g1 = normX; vtx.b1 = normY; vtx.a1 = bright;
304  }
305  else
306  {
307  vtx.r1 = 0.0f; vtx.g1 = 0.5f / 1.5f; vtx.b1 = 0.5f / 1.5f; vtx.a1 = 0.0f;
308  }
309 
310  // Second pass color
311  vtx.r2 = 0.0f; vtx.g2 = 0.5f / 1.5f; vtx.b2 = 0.5f / 1.5f; vtx.a2 = 0.0f;
312 
313  // Third pass color
314  float alpha; // 0.0 == fully transparent (takes old color), 1.0 == solid color (takes new color)
315  if (shadow)
316  {
317  alpha = 0.3 + 0.6 * light->getValue() * light->getLightness();
318  }
319  else // draw the edge of the light
320  {
321  alpha = 0.0;
322  }
323 
324  vtx.r3 = light->getR();
325  vtx.g3 = light->getG();
326  vtx.b3 = light->getB();
327  vtx.a3 = alpha;
328 }
329 
331 {
332  DrawVertex(x,y, false);
334 }
335 
337 {
338  DrawVertex(x,y, true);
340 }
341 
343  screen(screen), vbo_size(0), ibo_size(0)
344 {
345  glGenBuffers(2, bo);
346  vaoid = pGL->GenVAOID();
347 }
348 
350 {
351  glDeleteBuffers(2, bo);
352  pGL->FreeVAOID(vaoid);
353 }
354 
356 {
357 }
358 
360 {
361  // If we have nothing to draw (e.g. directly after initialization), abort early.
362  if (vertices.empty()) return;
363 
364  const GLuint vbo = bo[0];
365  const GLuint ibo = bo[1];
366 
367  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
368 
369  // Upload vertices
370  glBindBuffer(GL_ARRAY_BUFFER, vbo);
371  if (vbo_size < vertices.size())
372  {
373  glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STREAM_DRAW);
374  vbo_size = vertices.size();
375  }
376  else
377  {
378  glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(Vertex), &vertices[0]);
379  }
380  glBindBuffer(GL_ARRAY_BUFFER, 0);
381 
382  // Upload indices
383  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
384  if (ibo_size < triangulator.GetNIndices())
385  {
386  glBufferData(GL_ELEMENT_ARRAY_BUFFER, triangulator.GetNIndices() * sizeof(GLuint), triangulator.GetIndices(), GL_STREAM_DRAW);
387  ibo_size = triangulator.GetNIndices();
388  }
389  else
390  {
391  glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, triangulator.GetNIndices() * sizeof(GLuint), triangulator.GetIndices());
392  }
393  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
394 
395  GLuint vao;
396  const bool has_vao = pGL->GetVAO(vaoid, vao);
397  glBindVertexArray(vao);
398  if (!has_vao)
399  {
400  glBindBuffer(GL_ARRAY_BUFFER, vbo);
401  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
402  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Position));
403  glEnableVertexAttribArray(call.GetAttribute(C4FoWRSA_Color));
404 
405  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Position), 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, x)));
406  glVertexAttribPointer(call.GetAttribute(C4FoWRSA_Color), 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const uint8_t*>(offsetof(Vertex, r)));
407  }
408 
409  // Set Y offset for vertex
410  const float y_offset[] = { 0.0f, 0.0f };
411  call.SetUniform2fv(C4FoWRSU_VertexOffset, 1, y_offset);
412 
413  glDrawElements(GL_TRIANGLES, triangulator.GetNIndices(), GL_UNSIGNED_INT, nullptr);
414 
415  // Reset GL state
416  glBindVertexArray(0);
417  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
418 
419  // Assume the capacity stays the same:
420  vertices.resize(0);
422 }
423 
424 void C4FoWDrawWireframeStrategy::DrawVertex(Vertex& vtx)
425 {
426  // global coords -> screen pos and zoom
427  // TODO: We could do this in the shader...
428  vtx.x += screen->X - screen->TargetX;
429  vtx.y += screen->Y - screen->TargetY;
430  pGL->ApplyZoom(vtx.x, vtx.y);
431 }
432 
434 {
435  vertices.emplace_back();
436  Vertex& vtx = vertices.back();
437  vtx.x = x; vtx.y = y;
438 
439  switch(phase)
440  {
441  case P_None: return;
442  case P_Fade: vtx.r = 0.0f; vtx.g = 0.5f; vtx.b = 0.0f; vtx.a = 1.0f; break;
443  case P_Intermediate: vtx.r = 0.0f; vtx.g = 0.0f; vtx.b = 0.5f; vtx.a = 1.0f; break;
444  default: assert(false); // only fade has dark vertices
445  }
446 
447  DrawVertex(vtx);
449 }
450 
452 {
453  vertices.emplace_back();
454  Vertex& vtx = vertices.back();
455  vtx.x = x; vtx.y = y;
456 
457  switch(phase)
458  {
459  case P_None: return;
460  case P_Fan: vtx.r = 1.0f; vtx.g = 0.0f; vtx.b = 0.0f; vtx.a = 1.0f; break;
461  case P_FanMaxed: vtx.r = 1.0f; vtx.g = 1.0f; vtx.b = 0.0f; vtx.a = 1.0f; break;
462  case P_Fade: vtx.r = 0.0f; vtx.g = 1.0f; vtx.b = 0.0f; vtx.a = 1.0f; break;
463  case P_Intermediate: vtx.r = 0.0f; vtx.g = 0.0f; vtx.b = 1.0f; vtx.a = 1.0f; break;
464  default: assert(false);
465  }
466 
467  DrawVertex(vtx);
469 }
470 
471 #endif
CStdGL * pGL
Definition: C4DrawGL.cpp:907
@ C4FoWRSU_VertexOffset
Definition: C4FoW.h:85
@ C4FoWRSA_Color
Definition: C4FoW.h:92
@ C4FoWRSA_Position
Definition: C4FoW.h:91
T Clamp(T bval, T lbound, T rbound)
Definition: Standard.h:44
void ApplyZoom(float &X, float &Y)
Definition: C4Draw.cpp:778
float Y
Definition: C4Facet.h:118
float X
Definition: C4Facet.h:118
void Begin(const C4FoWRegion *region) override
void End(C4ShaderCall &call) override
void DrawDarkVertex(float x, float y) override
C4FoWDrawLightTextureStrategy(const C4FoWLight *light)
void DrawLightVertex(float x, float y) override
enum C4FoWDrawStrategy::DrawPhase P_None
virtual void DrawLightVertex(float x, float y)
virtual void End(C4ShaderCall &call)
C4FoWDrawTriangulator triangulator
virtual void DrawDarkVertex(float x, float y)
unsigned int GetNIndices() const
const unsigned int * GetIndices() const
void DrawDarkVertex(float x, float y) override
C4FoWDrawWireframeStrategy(const C4FoWLight *light, const C4TargetFacet *screen)
void Begin(const C4FoWRegion *region) override
void DrawLightVertex(float x, float y) override
void End(C4ShaderCall &call) override
float getBrightness() const
Definition: C4FoWLight.h:65
int32_t getX() const
Definition: C4FoWLight.h:58
float getB() const
Definition: C4FoWLight.h:68
float getValue() const
Definition: C4FoWLight.h:69
float getR() const
Definition: C4FoWLight.h:66
int32_t getNormalSize() const
Definition: C4FoWLight.h:64
float getLightness() const
Definition: C4FoWLight.h:70
int32_t getY() const
Definition: C4FoWLight.h:59
float getG() const
Definition: C4FoWLight.h:67
int32_t getSurfaceHeight() const
const C4Rect & getRegion() const
Definition: C4FoWRegion.h:53
int32_t getSurfaceWidth() const
int32_t y
Definition: C4Rect.h:30
int32_t x
Definition: C4Rect.h:30
GLint GetAttribute(int iAttribute) const
Definition: C4Shader.h:194
void SetUniform2fv(int iUniform, int iLength, const float *pVals) const
Definition: C4Shader.h:293
float TargetY
Definition: C4Facet.h:165
float TargetX
Definition: C4Facet.h:165
unsigned int GenVAOID()
Definition: C4DrawGL.cpp:927
void FreeVAOID(unsigned int vaoid)
Definition: C4DrawGL.cpp:969
bool GetVAO(unsigned int vaoid, GLuint &vao)
Definition: C4DrawGL.cpp:1015