1 #include "CUIDrawUtil.h"
2 
3 #include "ShaderProgram.h"
4 #include "../util/Logger.h"
5 #include "../util/Directories.h"
6 #include "../util/OptionsDB.h"
7 
8 #include <GG/DrawUtil.h>
9 
10 #include <cmath>
11 
12 
13 namespace {
FindIsoscelesTriangleVertices(const GG::Pt & ul,const GG::Pt & lr,ShapeOrientation orientation,double & x1_,double & y1_,double & x2_,double & y2_,double & x3_,double & y3_)14     void FindIsoscelesTriangleVertices(const GG::Pt& ul, const GG::Pt& lr, ShapeOrientation orientation,
15                                        double& x1_, double& y1_, double& x2_, double& y2_, double& x3_, double& y3_)
16     {
17         switch (orientation) {
18         case ShapeOrientation::UP:
19             x1_ = Value(ul.x);
20             y1_ = Value(lr.y);
21             x2_ = Value(lr.x);
22             y2_ = Value(lr.y);
23             x3_ = Value((ul.x + lr.x) / 2.0);
24             y3_ = Value(ul.y);
25             break;
26         case ShapeOrientation::DOWN:
27             x1_ = Value(lr.x);
28             y1_ = Value(ul.y);
29             x2_ = Value(ul.x);
30             y2_ = Value(ul.y);
31             x3_ = Value((ul.x + lr.x) / 2.0);
32             y3_ = Value(lr.y);
33             break;
34         case ShapeOrientation::LEFT:
35             x1_ = Value(lr.x);
36             y1_ = Value(lr.y);
37             x2_ = Value(lr.x);
38             y2_ = Value(ul.y);
39             x3_ = Value(ul.x);
40             y3_ = Value((ul.y + lr.y) / 2.0);
41             break;
42         default:
43             ErrorLogger() << "FindIsoscelesTriangleVertices passed invalid orientation";
44         case ShapeOrientation::RIGHT:
45             x1_ = Value(ul.x);
46             y1_ = Value(ul.y);
47             x2_ = Value(ul.x);
48             y2_ = Value(lr.y);
49             x3_ = Value(lr.x);
50             y3_ = Value((ul.y + lr.y) / 2.0);
51             break;
52         }
53     }
54 }
55 
BufferStoreCircleArcVertices(GG::GL2DVertexBuffer & buffer,const GG::Pt & ul,const GG::Pt & lr,double theta1,double theta2,bool filled_shape,int num_slices,bool fan)56 void BufferStoreCircleArcVertices(GG::GL2DVertexBuffer& buffer, const GG::Pt& ul, const GG::Pt& lr,
57                                   double theta1, double theta2, bool filled_shape, int num_slices, bool fan)
58 {
59     int wd = Value(lr.x - ul.x), ht = Value(lr.y - ul.y);
60     double center_x = Value(ul.x + wd / 2.0);
61     double center_y = Value(ul.y + ht / 2.0);
62     double r = std::min(wd / 2.0, ht / 2.0);
63     const double PI = 3.141594;
64 
65     // correct theta* values to range [0, 2pi)
66     if (theta1 < 0)
67         theta1 += (int(-theta1 / (2 * PI)) + 1) * 2 * PI;
68     else if (theta1 >= 2 * PI)
69         theta1 -= int(theta1 / (2 * PI)) * 2 * PI;
70     if (theta2 < 0)
71         theta2 += (int(-theta2 / (2 * PI)) + 1) * 2 * PI;
72     else if (theta2 >= 2 * PI)
73         theta2 -= int(theta2 / (2 * PI)) * 2 * PI;
74 
75     int SLICES = 50;
76     if (num_slices <= 0)
77         SLICES = std::min(std::max(12, 3 + std::max(wd, ht)), 50);  // this is a good guess at how much to tesselate the circle coordinates (50 segments max)
78     else
79         SLICES = num_slices;
80     const double   HORZ_THETA = (2 * PI) / SLICES;
81 
82     static std::map<int, std::vector<double>> unit_circle_coords;
83     std::vector<double>& unit_vertices = unit_circle_coords[SLICES];
84     bool calc_vertices = unit_vertices.size() == 0;
85     if (calc_vertices) {
86         unit_vertices.resize(2 * (SLICES + 1), 0.0);
87         double theta = 0.0f;
88         for (int j = 0; j <= SLICES; theta += HORZ_THETA, ++j) { // calculate x,y values for each point on a unit circle divided into SLICES arcs
89             unit_vertices[j*2] = std::cos(-theta);
90             unit_vertices[j*2+1] = std::sin(-theta);
91         }
92     }
93     int first_slice_idx = int(theta1 / HORZ_THETA + 1);
94     int last_slice_idx = int(theta2 / HORZ_THETA - 1);
95     if (theta1 >= theta2)
96         last_slice_idx += SLICES;
97 
98     if (fan) {  // store a triangle fan vertex list, specifying each vertex just once
99 
100         if (filled_shape)   // specify the central vertex first, to act as the pivot vertex for the fan
101             buffer.store(static_cast<GLfloat>(center_x),    static_cast<GLfloat>(center_y));
102         // if not filled_shape, assumes a previously-specified vertex in the buffer will act as the pivot for the fan
103 
104         // point on circle at angle theta1
105         double theta1_x = std::cos(-theta1), theta1_y = std::sin(-theta1);
106         buffer.store(static_cast<GLfloat>(center_x + theta1_x * r), static_cast<GLfloat>(center_y + theta1_y * r));
107 
108         // angles in between theta1 and theta2, if any
109         for (int i = first_slice_idx; i <= last_slice_idx + 1; ++i) {
110             int X = (i > SLICES ? (i - SLICES) : i) * 2, Y = X + 1;
111             buffer.store(static_cast<GLfloat>(center_x + unit_vertices[X] * r), static_cast<GLfloat>(center_y + unit_vertices[Y] * r));
112         }
113 
114         // theta2
115         double theta2_x = std::cos(-theta2), theta2_y = std::sin(-theta2);
116         buffer.store(static_cast<GLfloat>(center_x + theta2_x * r), static_cast<GLfloat>(center_y + theta2_y * r));
117 
118     } else {    // (not a fan) store a list of complete lines / triangles
119         // if storing a filled_shape, the first point in each triangle should be the centre of the arc
120         std::pair<GLfloat, GLfloat> first_point = {static_cast<GLfloat>(center_x), static_cast<GLfloat>(center_y)};
121         // (not used for non-filled-shape)
122 
123         // angles in between theta1 and theta2, if any
124         for (int i = first_slice_idx - 1; i <= last_slice_idx; ++i) {
125             if (filled_shape) {
126                 buffer.store(first_point.first, first_point.second);
127                 // list of triangles: need two more vertices on the arc per starting vertex
128             }
129             // else: list of lines, with two vertices each
130 
131             int X = (i > SLICES ? (i - SLICES) : i) * 2;
132             int Y = X + 1;
133             buffer.store(static_cast<GLfloat>(center_x + unit_vertices[X] * r), static_cast<GLfloat>(center_y + unit_vertices[Y] * r));
134 
135             int next_i = i + 1;
136             X = (next_i > SLICES ? (next_i - SLICES) : next_i) * 2;
137             Y = X + 1;
138             buffer.store(static_cast<GLfloat>(center_x + unit_vertices[X] * r), static_cast<GLfloat>(center_y + unit_vertices[Y] * r));
139         }
140 
141         // theta2
142         if (filled_shape) {
143             buffer.store(first_point.first, first_point.second);
144         }
145 
146         int i = last_slice_idx + 1;
147         int X = (i > SLICES ? (i - SLICES) : i) * 2;
148         int Y = X + 1;
149         buffer.store(static_cast<GLfloat>(center_x + unit_vertices[X] * r), static_cast<GLfloat>(center_y + unit_vertices[Y] * r));
150 
151         double theta2_x = std::cos(-theta2), theta2_y = std::sin(-theta2);
152         buffer.store(static_cast<GLfloat>(center_x + theta2_x * r), static_cast<GLfloat>(center_y + theta2_y * r));
153     }
154 }
155 
AdjustBrightness(GG::Clr & color,int amount,bool jointly_capped)156 void AdjustBrightness(GG::Clr& color, int amount, bool jointly_capped)
157 {
158     if (jointly_capped) {
159         int maxVal = std::max(std::max(color.r, color.g), color.b);
160         amount = std::min(amount, 255-maxVal);
161     }
162     color.r = static_cast<unsigned char>(std::max(0, std::min(color.r + amount, 255)));
163     color.g = static_cast<unsigned char>(std::max(0, std::min(color.g + amount, 255)));
164     color.b = static_cast<unsigned char>(std::max(0, std::min(color.b + amount, 255)));
165 }
166 
AdjustBrightness(GG::Clr & color,double amount,bool jointly_capped)167 void AdjustBrightness(GG::Clr& color, double amount, bool jointly_capped)
168 {
169     if (jointly_capped) {
170         int maxVal = std::max(std::max(color.r, color.g), color.b);
171         amount = std::min(amount, 255.0/maxVal);
172     }
173     color.r = static_cast<unsigned char>(std::max(0, std::min(static_cast<int>(color.r * amount), 255)));
174     color.g = static_cast<unsigned char>(std::max(0, std::min(static_cast<int>(color.g * amount), 255)));
175     color.b = static_cast<unsigned char>(std::max(0, std::min(static_cast<int>(color.b * amount), 255)));
176 }
177 
OpaqueColor(const GG::Clr & color)178 GG::Clr OpaqueColor(const GG::Clr& color)
179 {
180     GG::Clr retval = color;
181     retval.a = static_cast<unsigned char>(255);
182     return retval;
183 }
184 
BufferStoreRectangle(GG::GL2DVertexBuffer & buffer,const GG::Rect & area,const GG::Rect & border_thickness)185 void BufferStoreRectangle(GG::GL2DVertexBuffer& buffer,
186                           const GG::Rect& area,
187                           const GG::Rect& border_thickness)
188 {
189         GG::X inner_x1(area.ul.x + border_thickness.ul.x);
190         GG::Y inner_y1(area.ul.y + border_thickness.ul.y);
191         GG::X inner_x2(area.lr.x - border_thickness.lr.x);
192         GG::Y inner_y2(area.lr.y - border_thickness.lr.y);
193 
194         buffer.reserve(14);
195         buffer.store(inner_x2, inner_y1);
196         buffer.store(area.lr.x, area.ul.y);
197         buffer.store(inner_x1, inner_y1);
198         buffer.store(area.ul.x, area.ul.y);
199         buffer.store(inner_x1, inner_y2);
200         buffer.store(area.ul.x, area.lr.y);
201         buffer.store(inner_x2, inner_y2);
202         buffer.store(area.lr.x, area.lr.y);
203         buffer.store(inner_x2, inner_y1);
204         buffer.store(area.lr.x, area.ul.y);
205 
206         buffer.store(inner_x2, inner_y1);
207         buffer.store(inner_x1, inner_y1);
208         buffer.store(inner_x1, inner_y2);
209         buffer.store(inner_x2, inner_y2);
210 }
211 
AngledCornerRectangle(const GG::Pt & ul,const GG::Pt & lr,GG::Clr color,GG::Clr border,int angle_offset,int thick,bool upper_left_angled,bool lower_right_angled,bool draw_bottom)212 void AngledCornerRectangle(const GG::Pt& ul, const GG::Pt& lr, GG::Clr color, GG::Clr border, int angle_offset, int thick,
213                            bool upper_left_angled/* = true*/, bool lower_right_angled/* = true*/, bool draw_bottom/* = true*/)
214 {
215     glDisable(GL_TEXTURE_2D);
216 
217     GG::GL2DVertexBuffer vert_buf;
218     vert_buf.reserve(14);
219     GG::Pt thick_pt = GG::Pt(GG::X(thick), GG::Y(thick));
220     BufferStoreAngledCornerRectangleVertices(vert_buf, ul + thick_pt, lr - thick_pt, angle_offset,
221                                              upper_left_angled, lower_right_angled, draw_bottom);
222 
223     glDisable(GL_TEXTURE_2D);
224     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
225     glEnableClientState(GL_VERTEX_ARRAY);
226     glDisableClientState(GL_COLOR_ARRAY);
227     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
228 
229     vert_buf.activate();
230 
231     glColor(color);
232     glDrawArrays(GL_TRIANGLE_FAN, 0, vert_buf.size());
233     if (thick > 0) {
234         glColor(border);
235         glLineWidth(thick);
236         glDrawArrays(GL_LINE_STRIP, 0, vert_buf.size());
237         glLineWidth(1.0f);
238     }
239 
240     glPopClientAttrib();
241 
242     glEnable(GL_TEXTURE_2D);
243 }
244 
BufferStoreAngledCornerRectangleVertices(GG::GL2DVertexBuffer & buffer,const GG::Pt & ul,const GG::Pt & lr,int angle_offset,bool upper_left_angled,bool lower_right_angled,bool connect_bottom_line)245 void BufferStoreAngledCornerRectangleVertices(GG::GL2DVertexBuffer& buffer, const GG::Pt& ul, const GG::Pt& lr,
246                                               int angle_offset, bool upper_left_angled,
247                                               bool lower_right_angled, bool connect_bottom_line)
248 {
249     // these are listed in CCW order
250     if (connect_bottom_line)
251         buffer.store(Value(ul.x),                   Value(lr.y));
252 
253     if (lower_right_angled) {
254         buffer.store(Value(lr.x) - angle_offset - 3,Value(lr.y));   // don't know why, but - 3 here and the next line seem to make things symmetric top-left and bottom-right
255         buffer.store(Value(lr.x),                   Value(lr.y) - angle_offset - 3);
256     } else {
257         buffer.store(Value(lr.x),                   Value(lr.y));
258     }
259 
260     buffer.store(Value(lr.x),                       Value(ul.y));
261 
262     if (upper_left_angled) {
263         buffer.store(Value(ul.x) + angle_offset,    Value(ul.y));
264         buffer.store(Value(ul.x),                   Value(ul.y) + angle_offset);
265     } else {
266         buffer.store(Value(ul.x),                   Value(ul.y));
267     }
268 
269     buffer.store(Value(ul.x),                       Value(lr.y));
270 }
271 
InAngledCornerRect(const GG::Pt & pt,const GG::Pt & ul,const GG::Pt & lr,int angle_offset,bool upper_left_angled,bool lower_right_angled)272 bool InAngledCornerRect(const GG::Pt& pt, const GG::Pt& ul, const GG::Pt& lr, int angle_offset,
273                         bool upper_left_angled/* = true*/, bool lower_right_angled)
274 {
275     bool retval = false;
276     if ((retval = ((ul <= pt) && (pt < lr)))) {
277         GG::Pt dist_from_ul = pt - ul;
278         GG::Pt dist_from_lr = lr - pt;
279         bool inside_upper_left_corner = upper_left_angled ? (angle_offset < Value(dist_from_ul.x) + Value(dist_from_ul.y)) : true;
280         bool inside_lower_right_corner = lower_right_angled ? (angle_offset < Value(dist_from_lr.x) + Value(dist_from_lr.y)) : true;
281         retval = inside_upper_left_corner && inside_lower_right_corner;
282     }
283     return retval;
284 }
285 
Triangle(double x1,double y1,double x2,double y2,double x3,double y3,GG::Clr color,bool border)286 void Triangle(double x1, double y1, double x2, double y2, double x3, double y3, GG::Clr color, bool border) {
287     GG::Clr border_clr = color;
288     if (border)
289         AdjustBrightness(border_clr, 75);
290     GG::Triangle(GG::Pt(GG::X(x1), GG::Y(y1)),
291                  GG::Pt(GG::X(x2), GG::Y(y2)),
292                  GG::Pt(GG::X(x3), GG::Y(y3)),
293                  color, border ? border_clr : GG::CLR_ZERO);
294 }
295 
InTriangle(const GG::Pt & pt,double x1,double y1,double x2,double y2,double x3,double y3)296 bool InTriangle(const GG::Pt& pt, double x1, double y1, double x2, double y2, double x3, double y3) {
297     double vec_A_x = x2 - x1; // side A is the vector from pt1 to pt2
298     double vec_A_y = y2 - y1; // side A is the vector from pt1 to pt2
299     double vec_B_x = x3 - x2; // side B is the vector from pt2 to pt3
300     double vec_B_y = y3 - y2; // side B is the vector from pt2 to pt3
301     double vec_C_x = x1 - x3; // side C is the vector from pt3 to pt1
302     double vec_C_y = y1 - y3; // side C is the vector from pt3 to pt1
303     int pt_x = Value(pt.x);
304     int pt_y = Value(pt.y);
305     // take dot products of perpendicular vectors (normals of sides) with point pt, and sum the signs of these products
306     int sum = (0 < (pt_x - x1) * vec_A_y + (pt_y - y1) * -vec_A_x ? 1 : 0) +
307               (0 < (pt_x - x2) * vec_B_y + (pt_y - y2) * -vec_B_x ? 1 : 0) +
308               (0 < (pt_x - x3) * vec_C_y + (pt_y - y3) * -vec_C_x ? 1 : 0);
309     // if the products are all the same sign, the point is in the triangle
310     return (sum == 3 || sum == 0);
311 }
312 
IsoscelesTriangle(const GG::Pt & ul,const GG::Pt & lr,ShapeOrientation orientation,GG::Clr color,bool border)313 void IsoscelesTriangle(const GG::Pt& ul, const GG::Pt& lr, ShapeOrientation orientation, GG::Clr color, bool border) {
314     double x1_, y1_, x2_, y2_, x3_, y3_;
315     FindIsoscelesTriangleVertices(ul, lr, orientation, x1_, y1_, x2_, y2_, x3_, y3_);
316     Triangle(x1_, y1_, x2_, y2_, x3_, y3_, color, border);
317 }
318 
BufferStoreIsoscelesTriangle(GG::GL2DVertexBuffer & buffer,const GG::Pt & ul,const GG::Pt & lr,ShapeOrientation orientation)319 void BufferStoreIsoscelesTriangle(GG::GL2DVertexBuffer& buffer, const GG::Pt& ul, const GG::Pt& lr, ShapeOrientation orientation) {
320     double x1_, y1_, x2_, y2_, x3_, y3_;
321     FindIsoscelesTriangleVertices(ul, lr, orientation, x1_, y1_, x2_, y2_, x3_, y3_);
322     buffer.store(x1_,   y1_);
323     buffer.store(x2_,   y2_);
324     buffer.store(x3_,   y3_);
325 }
326 
InIsoscelesTriangle(const GG::Pt & pt,const GG::Pt & ul,const GG::Pt & lr,ShapeOrientation orientation)327 bool InIsoscelesTriangle(const GG::Pt& pt, const GG::Pt& ul, const GG::Pt& lr,
328                          ShapeOrientation orientation)
329 {
330     double x1_, y1_, x2_, y2_, x3_, y3_;
331     FindIsoscelesTriangleVertices(ul, lr, orientation, x1_, y1_, x2_, y2_, x3_, y3_);
332     return InTriangle(pt, x1_, y1_, x2_, y2_, x3_, y3_);
333 }
334 
CircleArc(const GG::Pt & ul,const GG::Pt & lr,double theta1,double theta2,bool filled_shape)335 void CircleArc(const GG::Pt& ul, const GG::Pt& lr, double theta1, double theta2,
336                bool filled_shape)
337 {
338     //std::cout << "CircleArc ul: " << ul << "  lr: " << lr << "  theta1: " << theta1 << "  theta2: " << theta2 << "  filled: " << filled_shape << std::endl;
339     GG::GL2DVertexBuffer vert_buf;
340     vert_buf.reserve(50);   // max number that BufferStoreCircleArcVertices might add
341 
342     BufferStoreCircleArcVertices(vert_buf, ul, lr, theta1, theta2, filled_shape, 0, true);
343 
344     //glDisable(GL_TEXTURE_2D);
345     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
346     glEnableClientState(GL_VERTEX_ARRAY);
347     glDisableClientState(GL_COLOR_ARRAY);
348     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
349 
350     vert_buf.activate();
351 
352     if (filled_shape)
353         glDrawArrays(GL_TRIANGLE_FAN, 0, vert_buf.size());
354     else
355         glDrawArrays(GL_LINE_STRIP, 0, vert_buf.size());
356 
357     glPopClientAttrib();
358     //glEnable(GL_TEXTURE_2D);
359 }
360 
PartlyRoundedRect(const GG::Pt & ul,const GG::Pt & lr,int radius,bool ur_round,bool ul_round,bool ll_round,bool lr_round,bool fill)361 void PartlyRoundedRect(const GG::Pt& ul, const GG::Pt& lr, int radius,
362                        bool ur_round, bool ul_round,
363                        bool ll_round, bool lr_round, bool fill)
364 {
365     GG::GL2DVertexBuffer vert_buf;
366     vert_buf.reserve(210);  // should be enough for 4 corners with 50 verts each, plus a bit extra to be safe
367 
368     BufferStorePartlyRoundedRectVertices(vert_buf, ul, lr, radius, ur_round,
369                                          lr_round, ll_round, lr_round);
370 
371     //glDisable(GL_TEXTURE_2D);
372     glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
373     glEnableClientState(GL_VERTEX_ARRAY);
374     glDisableClientState(GL_COLOR_ARRAY);
375     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
376 
377     vert_buf.activate();
378 
379     if (fill)
380         glDrawArrays(GL_TRIANGLE_FAN, 0, vert_buf.size());
381     else
382         glDrawArrays(GL_LINE_LOOP, 0, vert_buf.size());
383 
384     glPopClientAttrib();
385     //glEnable(GL_TEXTURE_2D);
386 }
387 
BufferStorePartlyRoundedRectVertices(GG::GL2DVertexBuffer & buffer,const GG::Pt & ul,const GG::Pt & lr,int radius,bool ur_round,bool ul_round,bool ll_round,bool lr_round)388 void BufferStorePartlyRoundedRectVertices(GG::GL2DVertexBuffer& buffer, const GG::Pt& ul,
389                                           const GG::Pt& lr, int radius, bool ur_round,
390                                           bool ul_round, bool ll_round, bool lr_round)
391 {
392     const double PI = 3.141594;
393 
394     buffer.store(lr.x, ul.y + radius);
395 
396     if (ur_round)
397         BufferStoreCircleArcVertices(buffer, GG::Pt(lr.x - 2 * radius, ul.y),
398                                      GG::Pt(lr.x, ul.y + 2 * radius), 0.0, PI / 2.0, false);
399     else
400         buffer.store(lr.x, ul.y);
401 
402     if (ul_round)
403         BufferStoreCircleArcVertices(buffer, ul, GG::Pt(ul.x + 2 * radius, ul.y + 2 * radius),
404                                      PI / 2.0, PI, false);
405     else
406         buffer.store(ul.x, ul.y);
407 
408     if (ll_round)
409         BufferStoreCircleArcVertices(buffer, GG::Pt(ul.x, lr.y - 2 * radius),
410                                      GG::Pt(ul.x + 2 * radius, lr.y),
411                                      PI, 3.0 * PI / 2.0, false);
412     else
413         buffer.store(ul.x, lr.y);
414 
415     if (lr_round)
416         BufferStoreCircleArcVertices(buffer, GG::Pt(lr.x - 2 * radius, lr.y - 2 * radius),
417                                      lr, 3.0 * PI / 2.0, 0.0, false);
418     else
419         buffer.store(lr.x, lr.y);
420 
421     buffer.store(lr.x, ul.y + radius);
422 }
423 
424 namespace {
425     const double TWO_PI = 2.0 * 3.14159;
426 }
427 
428 class ScanlineRenderer::Impl {
429 public:
Impl()430     Impl() :
431         m_scanline_shader(),
432         m_failed_init(false)
433     { m_color = GG::CLR_BLACK; }
434 
StartUsing()435     void StartUsing() {
436         if (m_failed_init)
437             return;
438 
439         if (!m_scanline_shader) {
440             boost::filesystem::path shader_path = GetRootDataDir() / "default" / "shaders" / "scanlines.frag";
441             std::string shader_text;
442             if (!ReadFile(shader_path, shader_text)) {
443                 ErrorLogger() << "ScanlineRenderer failed to read shader at path " << shader_path.string();
444                 m_failed_init = true;
445                 return;
446             }
447             m_scanline_shader = ShaderProgram::shaderProgramFactory("", shader_text);
448 
449             if (!m_scanline_shader) {
450                 ErrorLogger() << "ScanlineRenderer failed to initialize shader.";
451                 m_failed_init = true;
452                 return;
453             }
454         }
455 
456         float fog_scanline_spacing = static_cast<float>(GetOptionsDB().Get<double>("ui.map.system.scanlines.spacing"));
457         m_scanline_shader->Use();
458         m_scanline_shader->Bind("scanline_spacing", fog_scanline_spacing);
459         m_scanline_shader->Bind("line_color", m_color.r * (1.f / 255.f), m_color.g * (1.f / 255.f), m_color.b * (1.f / 255.f), m_color.a * (1.f / 255.f));
460     }
461 
SetColor(GG::Clr clr)462     void SetColor(GG::Clr clr) {
463         m_color = clr;
464     }
465 
StopUsing()466     void StopUsing()
467     { m_scanline_shader->stopUse(); }
468 
RenderCircle(const GG::Pt & ul,const GG::Pt & lr)469     void RenderCircle(const GG::Pt& ul, const GG::Pt& lr) {
470         StartUsing();
471         CircleArc(ul, lr, 0.0, TWO_PI, true);
472         StopUsing();
473     }
474 
RenderRectangle(const GG::Pt & ul,const GG::Pt & lr)475     void RenderRectangle(const GG::Pt& ul, const GG::Pt& lr) {
476         StartUsing();
477         GG::FlatRectangle(ul, lr, GG::CLR_WHITE, GG::CLR_WHITE, 0u);
478         StopUsing();
479     }
480 
481     std::unique_ptr<ShaderProgram> m_scanline_shader;
482     bool m_failed_init;
483     GG::Clr m_color;
484 };
485 
486 
ScanlineRenderer()487 ScanlineRenderer::ScanlineRenderer() :
488     m_impl(new Impl())
489 {}
490 
491 // This destructor is required here because ~ScanlineRendererImpl is declared here.
492 ScanlineRenderer::~ScanlineRenderer() = default;
493 
RenderCircle(const GG::Pt & ul,const GG::Pt & lr)494 void ScanlineRenderer::RenderCircle(const GG::Pt& ul, const GG::Pt& lr)
495 { m_impl->RenderCircle(ul, lr); }
496 
RenderRectangle(const GG::Pt & ul,const GG::Pt & lr)497 void ScanlineRenderer::RenderRectangle(const GG::Pt& ul, const GG::Pt& lr)
498 { m_impl->RenderRectangle(ul, lr); }
499 
StartUsing()500 void ScanlineRenderer::StartUsing()
501 { m_impl->StartUsing(); }
502 
SetColor(GG::Clr clr)503 void ScanlineRenderer::SetColor(GG::Clr clr)
504 { m_impl->SetColor(clr); }
505 
StopUsing()506 void ScanlineRenderer::StopUsing()
507 { m_impl->StopUsing(); }
508