1 #include "renderer.h"
2 #include "rendersettings.h"
3 #include "Geometry.h"
4 #include "polyset.h"
5 #include "Polygon2d.h"
6 #include "colormap.h"
7 #include "printutils.h"
8 #include "feature.h"
9
10 #include "polyset-utils.h"
11 #include "grid.h"
12 #include <Eigen/LU>
13
Renderer()14 Renderer::Renderer() : colorscheme(nullptr)
15 {
16 PRINTD("Renderer() start");
17
18 renderer_shader.progid = 0;
19
20 // Setup default colors
21 // The main colors, MATERIAL and CUTOUT, come from this object's
22 // colorscheme. Colorschemes don't currently hold information
23 // for Highlight/Background colors
24 // but it wouldn't be too hard to make them do so.
25
26 // MATERIAL is set by this object's colorscheme
27 // CUTOUT is set by this object's colorscheme
28 colormap[ColorMode::HIGHLIGHT] = {255, 81, 81, 128};
29 colormap[ColorMode::BACKGROUND] = {180, 180, 180, 128};
30 // MATERIAL_EDGES is set by this object's colorscheme
31 // CUTOUT_EDGES is set by this object's colorscheme
32 colormap[ColorMode::HIGHLIGHT_EDGES] = {255, 171, 86, 128};
33 colormap[ColorMode::BACKGROUND_EDGES] = {150, 150, 150, 128};
34
35 setColorScheme(ColorMap::inst()->defaultColorScheme());
36
37 const char *vs_source = R"VS_PROG(
38 #version 110
39
40 uniform vec4 color1; // face color
41 uniform vec4 color2; // edge color
42 attribute vec3 barycentric; // barycentric form of vertex coord
43 // either [1,0,0], [0,1,0] or [0,0,1] under normal circumstances (no edges disabled)
44 varying vec3 vBC; // varying barycentric coords
45 varying float shading; // multiplied by color1. color2 is without lighting
46
47 void main(void) {
48 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
49 vBC = barycentric;
50 vec3 normal, lightDir;
51 normal = normalize(gl_NormalMatrix * gl_Normal);
52 lightDir = normalize(vec3(gl_LightSource[0].position));
53 shading = 0.2 + abs(dot(normal, lightDir));
54 }
55 )VS_PROG";
56
57 const char *fs_source = R"FS_PROG(
58 #version 110
59
60 uniform vec4 color1, color2;
61 varying vec3 vBC;
62 varying float shading;
63
64 vec3 smoothstep3f(vec3 edge0, vec3 edge1, vec3 x) {
65 vec3 t;
66 t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
67 return t * t * (3.0 - 2.0 * t);
68 }
69
70 float edgeFactor() {
71 const float th = 1.414; // total thickness of half-edge (per triangle) including fade, (must be >= fade)
72 const float fade = 1.414; // thickness of fade (antialiasing) in screen pixels
73 vec3 d = fwidth(vBC);
74 vec3 a3 = smoothstep((th-fade)*d, th*d, vBC);
75 return min(min(a3.x, a3.y), a3.z);
76 }
77
78 void main(void) {
79 gl_FragColor = mix(color2, vec4(color1.rgb * shading, color1.a), edgeFactor());
80 }
81 )FS_PROG";
82
83 GLint status;
84 GLenum err;
85 auto vs = glCreateShader(GL_VERTEX_SHADER);
86 glShaderSource(vs, 1, (const GLchar**)&vs_source, nullptr);
87 glCompileShader(vs);
88 err = glGetError();
89 if (err != GL_NO_ERROR) {
90 PRINTDB("OpenGL Error: %s\n", gluErrorString(err));
91 return;
92 }
93 glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
94 if (status == GL_FALSE) {
95 int loglen;
96 char logbuffer[1000];
97 glGetShaderInfoLog(vs, sizeof(logbuffer), &loglen, logbuffer);
98 PRINTDB("OpenGL Program Compile Vertex Shader Error:\n%s", logbuffer);
99 return;
100 }
101
102 auto fs = glCreateShader(GL_FRAGMENT_SHADER);
103 glShaderSource(fs, 1, (const GLchar**)&fs_source, nullptr);
104 glCompileShader(fs);
105 err = glGetError();
106 if (err != GL_NO_ERROR) {
107 PRINTDB("OpenGL Error: %s\n", gluErrorString(err));
108 return;
109 }
110 glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
111 if (status == GL_FALSE) {
112 int loglen;
113 char logbuffer[1000];
114 glGetShaderInfoLog(fs, sizeof(logbuffer), &loglen, logbuffer);
115 PRINTDB("OpenGL Program Compile Fragement Shader Error:\n%s", logbuffer);
116 return;
117 }
118
119 auto edgeshader_prog = glCreateProgram();
120 glAttachShader(edgeshader_prog, vs);
121 glAttachShader(edgeshader_prog, fs);
122 glLinkProgram(edgeshader_prog);
123
124 err = glGetError();
125 if (err != GL_NO_ERROR) {
126 PRINTDB("OpenGL Error: %s\n", gluErrorString(err));
127 return;
128 }
129
130 glGetProgramiv(edgeshader_prog, GL_LINK_STATUS, &status);
131 if (status == GL_FALSE) {
132 int loglen;
133 char logbuffer[1000];
134 glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer);
135 PRINTDB("OpenGL Program Linker Error:\n%s", logbuffer);
136 return;
137 }
138
139 int loglen;
140 char logbuffer[1000];
141 glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer);
142 if (loglen > 0) {
143 PRINTDB("OpenGL Program Link OK:\n%s", logbuffer);
144 }
145 glValidateProgram(edgeshader_prog);
146 glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer);
147 if (loglen > 0) {
148 PRINTDB("OpenGL Program Validation results:\n%s", logbuffer);
149 }
150
151 renderer_shader.progid = edgeshader_prog; // 0
152 renderer_shader.type = EDGE_RENDERING;
153 renderer_shader.data.csg_rendering.color_area = glGetUniformLocation(edgeshader_prog, "color1"); // 1
154 renderer_shader.data.csg_rendering.color_edge = glGetUniformLocation(edgeshader_prog, "color2"); // 2
155 renderer_shader.data.csg_rendering.barycentric = glGetAttribLocation(edgeshader_prog, "barycentric"); // 3
156
157 PRINTD("Renderer() end");
158 }
159
resize(int,int)160 void Renderer::resize(int /*w*/, int /*h*/)
161 {
162 }
163
getColor(Renderer::ColorMode colormode,Color4f & col) const164 bool Renderer::getColor(Renderer::ColorMode colormode, Color4f &col) const
165 {
166 if (colormode==ColorMode::NONE) return false;
167 if (colormap.count(colormode) > 0) {
168 col = colormap.at(colormode);
169 return true;
170 }
171 return false;
172 }
173
get_csgmode(const bool highlight_mode,const bool background_mode,const OpenSCADOperator type) const174 Renderer::csgmode_e Renderer::get_csgmode(const bool highlight_mode, const bool background_mode, const OpenSCADOperator type) const {
175 int csgmode = highlight_mode ? CSGMODE_HIGHLIGHT : (background_mode ? CSGMODE_BACKGROUND : CSGMODE_NORMAL);
176 if (type == OpenSCADOperator::DIFFERENCE) csgmode |= CSGMODE_DIFFERENCE_FLAG;
177 return csgmode_e(csgmode);
178 }
179
setColor(const float color[4],const shaderinfo_t * shaderinfo) const180 void Renderer::setColor(const float color[4], const shaderinfo_t *shaderinfo) const
181 {
182 if (shaderinfo && shaderinfo->type != EDGE_RENDERING) {
183 return;
184 }
185
186 PRINTD("setColor a");
187 Color4f col;
188 getColor(ColorMode::MATERIAL,col);
189 float c[4] = {color[0], color[1], color[2], color[3]};
190 if (c[0] < 0) c[0] = col[0];
191 if (c[1] < 0) c[1] = col[1];
192 if (c[2] < 0) c[2] = col[2];
193 if (c[3] < 0) c[3] = col[3];
194 glColor4fv(c);
195 #ifdef ENABLE_OPENCSG
196 if (shaderinfo) {
197 glUniform4f(shaderinfo->data.csg_rendering.color_area, c[0], c[1], c[2], c[3]);
198 glUniform4f(shaderinfo->data.csg_rendering.color_edge, (c[0]+1)/2, (c[1]+1)/2, (c[2]+1)/2, 1.0);
199 }
200 #endif
201 }
202
203 // returns the color which has been set, which may differ from the color input parameter
setColor(ColorMode colormode,const float color[4],const shaderinfo_t * shaderinfo) const204 Color4f Renderer::setColor(ColorMode colormode, const float color[4], const shaderinfo_t *shaderinfo) const
205 {
206 PRINTD("setColor b");
207 Color4f basecol;
208 if (getColor(colormode, basecol)) {
209 if (colormode == ColorMode::BACKGROUND) {
210 basecol = {color[0] >= 0 ? color[0] : basecol[0],
211 color[1] >= 0 ? color[1] : basecol[1],
212 color[2] >= 0 ? color[2] : basecol[2],
213 color[3] >= 0 ? color[3] : basecol[3]};
214 }
215 else if (colormode != ColorMode::HIGHLIGHT) {
216 basecol = {color[0] >= 0 ? color[0] : basecol[0],
217 color[1] >= 0 ? color[1] : basecol[1],
218 color[2] >= 0 ? color[2] : basecol[2],
219 color[3] >= 0 ? color[3] : basecol[3]};
220 }
221 setColor(basecol.data(), shaderinfo);
222 }
223 return basecol;
224 }
225
setColor(ColorMode colormode,const shaderinfo_t * shaderinfo) const226 void Renderer::setColor(ColorMode colormode, const shaderinfo_t *shaderinfo) const
227 {
228 PRINTD("setColor c");
229 float c[4] = {-1,-1,-1,-1};
230 setColor(colormode, c, shaderinfo);
231 }
232
233 /* fill this->colormap with matching entries from the colorscheme. note
234 this does not change Highlight or Background colors as they are not
235 represented in the colorscheme (yet). Also edgecolors are currently the
236 same for CGAL & OpenCSG */
setColorScheme(const ColorScheme & cs)237 void Renderer::setColorScheme(const ColorScheme &cs) {
238 PRINTD("setColorScheme");
239 colormap[ColorMode::MATERIAL] = ColorMap::getColor(cs, RenderColor::OPENCSG_FACE_FRONT_COLOR);
240 colormap[ColorMode::CUTOUT] = ColorMap::getColor(cs, RenderColor::OPENCSG_FACE_BACK_COLOR);
241 colormap[ColorMode::MATERIAL_EDGES] = ColorMap::getColor(cs, RenderColor::CGAL_EDGE_FRONT_COLOR);
242 colormap[ColorMode::CUTOUT_EDGES] = ColorMap::getColor(cs, RenderColor::CGAL_EDGE_BACK_COLOR);
243 colormap[ColorMode::EMPTY_SPACE] = ColorMap::getColor(cs, RenderColor::BACKGROUND_COLOR);
244 this->colorscheme = &cs;
245 }
246
247 #ifdef ENABLE_OPENCSG
draw_triangle(const Renderer::shaderinfo_t * shaderinfo,const Vector3d & p0,const Vector3d & p1,const Vector3d & p2,bool e0,bool e1,bool e2,double z,bool mirror)248 static void draw_triangle(const Renderer::shaderinfo_t *shaderinfo, const Vector3d &p0, const Vector3d &p1, const Vector3d &p2,
249 bool e0, bool e1, bool e2, double z, bool mirror)
250 {
251 Renderer::shader_type_t type =
252 (shaderinfo) ? shaderinfo->type : Renderer::NONE;
253
254 // e0,e1,e2 are used to disable some edges from display.
255 // Edges are numbered to correspond with the vertex opposite of them.
256 // The edge shader draws edges when the minimum component of barycentric coords is near 0
257 // Disabled edges have their corresponding components set to 1.0 when they would otherwise be 0.0.
258 double d0 = e0 ? 0.0 : 1.0;
259 double d1 = e1 ? 0.0 : 1.0;
260 double d2 = e2 ? 0.0 : 1.0;
261
262 switch (type) {
263 case Renderer::EDGE_RENDERING:
264 if (mirror) {
265 glVertexAttrib3f(shaderinfo->data.csg_rendering.barycentric, 1.0, d1, d2);
266 glVertex3f(p0[0], p0[1], p0[2] + z);
267 glVertexAttrib3f(shaderinfo->data.csg_rendering.barycentric, d0, d1, 1.0);
268 glVertex3f(p2[0], p2[1], p2[2] + z);
269 glVertexAttrib3f(shaderinfo->data.csg_rendering.barycentric, d0, 1.0, d2);
270 glVertex3f(p1[0], p1[1], p1[2] + z);
271 } else {
272 glVertexAttrib3f(shaderinfo->data.csg_rendering.barycentric, 1.0, d1, d2);
273 glVertex3f(p0[0], p0[1], p0[2] + z);
274 glVertexAttrib3f(shaderinfo->data.csg_rendering.barycentric, d0, 1.0, d2);
275 glVertex3f(p1[0], p1[1], p1[2] + z);
276 glVertexAttrib3f(shaderinfo->data.csg_rendering.barycentric, d0, d1, 1.0);
277 glVertex3f(p2[0], p2[1], p2[2] + z);
278 }
279 break;
280 default:
281 case Renderer::SELECT_RENDERING:
282 glVertex3d(p0[0], p0[1], p0[2] + z);
283 if (!mirror) {
284 glVertex3d(p1[0], p1[1], p1[2] + z);
285 }
286 glVertex3d(p2[0], p2[1], p2[2] + z);
287 if (mirror) {
288 glVertex3d(p1[0], p1[1], p1[2] + z);
289 }
290 }
291 }
292 #endif
293
294 #ifndef NULLGL
draw_tri(const Vector3d & p0,const Vector3d & p1,const Vector3d & p2,double z,bool mirror)295 static void draw_tri(const Vector3d &p0, const Vector3d &p1, const Vector3d &p2, double z, bool mirror)
296 {
297 glVertex3d(p0[0], p0[1], p0[2] + z);
298 if (!mirror) glVertex3d(p1[0], p1[1], p1[2] + z);
299 glVertex3d(p2[0], p2[1], p2[2] + z);
300 if (mirror) glVertex3d(p1[0], p1[1], p1[2] + z);
301 }
302
gl_draw_triangle(const Renderer::shaderinfo_t * shaderinfo,const Vector3d & p0,const Vector3d & p1,const Vector3d & p2,bool e0,bool e1,bool e2,double z,bool mirrored)303 static void gl_draw_triangle(const Renderer::shaderinfo_t *shaderinfo, const Vector3d &p0, const Vector3d &p1, const Vector3d &p2, bool e0, bool e1, bool e2, double z, bool mirrored)
304 {
305 double ax = p1[0] - p0[0], bx = p1[0] - p2[0];
306 double ay = p1[1] - p0[1], by = p1[1] - p2[1];
307 double az = p1[2] - p0[2], bz = p1[2] - p2[2];
308 double nx = ay*bz - az*by;
309 double ny = az*bx - ax*bz;
310 double nz = ax*by - ay*bx;
311 double nl = sqrt(nx*nx + ny*ny + nz*nz);
312 glNormal3d(nx / nl, ny / nl, nz / nl);
313 #ifdef ENABLE_OPENCSG
314 if (shaderinfo) {
315 draw_triangle(shaderinfo, p0, p1, p2, e0, e1, e2, z, mirrored);
316 }
317 else
318 #endif
319 {
320 draw_tri(p0, p1, p2, z, mirrored);
321 }
322 }
323
render_surface(const PolySet & ps,csgmode_e csgmode,const Transform3d & m,const shaderinfo_t * shaderinfo) const324 void Renderer::render_surface(const PolySet &ps, csgmode_e csgmode, const Transform3d &m, const shaderinfo_t *shaderinfo) const
325 {
326 PRINTD("Renderer render");
327 bool mirrored = m.matrix().determinant() < 0;
328
329 if (ps.getDimension() == 2) {
330 // Render 2D objects 1mm thick, but differences slightly larger
331 double zbase = 1 + ((csgmode & CSGMODE_DIFFERENCE_FLAG) ? 0.1 : 0);
332 glBegin(GL_TRIANGLES);
333
334 // Render top+bottom
335 for (double z = -zbase/2; z < zbase; z += zbase) {
336 for (size_t i = 0; i < ps.polygons.size(); ++i) {
337 const Polygon *poly = &ps.polygons[i];
338 if (poly->size() == 3) {
339 if (z < 0) {
340 gl_draw_triangle(shaderinfo, poly->at(0), poly->at(2), poly->at(1), true, true, true, z, mirrored);
341 } else {
342 gl_draw_triangle(shaderinfo, poly->at(0), poly->at(1), poly->at(2), true, true, true, z, mirrored);
343 }
344 }
345 else if (poly->size() == 4) {
346 if (z < 0) {
347 gl_draw_triangle(shaderinfo, poly->at(0), poly->at(3), poly->at(1), false, true, true, z, mirrored);
348 gl_draw_triangle(shaderinfo, poly->at(2), poly->at(1), poly->at(3), false, true, true, z, mirrored);
349 } else {
350 gl_draw_triangle(shaderinfo, poly->at(0), poly->at(1), poly->at(3), false, true, true, z, mirrored);
351 gl_draw_triangle(shaderinfo, poly->at(2), poly->at(3), poly->at(1), false, true, true, z, mirrored);
352 }
353 }
354 else {
355 Vector3d center = Vector3d::Zero();
356 for (size_t j = 0; j < poly->size(); ++j) {
357 center[0] += poly->at(j)[0];
358 center[1] += poly->at(j)[1];
359 }
360 center[0] /= poly->size();
361 center[1] /= poly->size();
362 for (size_t j = 1; j <= poly->size(); ++j) {
363 if (z < 0) {
364 gl_draw_triangle(shaderinfo, center, poly->at(j % poly->size()), poly->at(j - 1),
365 true, false, false, z, mirrored);
366 } else {
367 gl_draw_triangle(shaderinfo, center, poly->at(j - 1), poly->at(j % poly->size()),
368 true, false, false, z, mirrored);
369 }
370 }
371 }
372 }
373 }
374
375 // Render sides
376 if (ps.getPolygon().outlines().size() > 0) {
377 for (const Outline2d &o : ps.getPolygon().outlines()) {
378 for (size_t j = 1; j <= o.vertices.size(); ++j) {
379 Vector3d p1(o.vertices[j-1][0], o.vertices[j-1][1], -zbase/2);
380 Vector3d p2(o.vertices[j-1][0], o.vertices[j-1][1], zbase/2);
381 Vector3d p3(o.vertices[j % o.vertices.size()][0], o.vertices[j % o.vertices.size()][1], -zbase/2);
382 Vector3d p4(o.vertices[j % o.vertices.size()][0], o.vertices[j % o.vertices.size()][1], zbase/2);
383 gl_draw_triangle(shaderinfo, p2, p1, p3, true, false, true, 0, mirrored);
384 gl_draw_triangle(shaderinfo, p2, p3, p4, true, true, false, 0, mirrored);
385 }
386 }
387 }
388 else {
389 // If we don't have borders, use the polygons as borders.
390 // FIXME: When is this used?
391 const Polygons *borders_p = &ps.polygons;
392 for (size_t i = 0; i < borders_p->size(); ++i) {
393 const Polygon *poly = &borders_p->at(i);
394 for (size_t j = 1; j <= poly->size(); ++j) {
395 Vector3d p1 = poly->at(j - 1), p2 = poly->at(j - 1);
396 Vector3d p3 = poly->at(j % poly->size()), p4 = poly->at(j % poly->size());
397 p1[2] -= zbase/2, p2[2] += zbase/2;
398 p3[2] -= zbase/2, p4[2] += zbase/2;
399 gl_draw_triangle(shaderinfo, p2, p1, p3, true, false, true, 0, mirrored);
400 gl_draw_triangle(shaderinfo, p2, p3, p4, true, true, false, 0, mirrored);
401 }
402 }
403 }
404 glEnd();
405 } else if (ps.getDimension() == 3) {
406 for (size_t i = 0; i < ps.polygons.size(); ++i) {
407 const Polygon *poly = &ps.polygons[i];
408 glBegin(GL_TRIANGLES);
409 if (poly->size() == 3) {
410 gl_draw_triangle(shaderinfo, poly->at(0), poly->at(1), poly->at(2), true, true, true, 0, mirrored);
411 }
412 else if (poly->size() == 4) {
413 gl_draw_triangle(shaderinfo, poly->at(0), poly->at(1), poly->at(3), false, true, true, 0, mirrored);
414 gl_draw_triangle(shaderinfo, poly->at(2), poly->at(3), poly->at(1), false, true, true, 0, mirrored);
415 }
416 else {
417 Vector3d center = Vector3d::Zero();
418 for (size_t j = 0; j < poly->size(); ++j) {
419 center[0] += poly->at(j)[0];
420 center[1] += poly->at(j)[1];
421 center[2] += poly->at(j)[2];
422 }
423 center[0] /= poly->size();
424 center[1] /= poly->size();
425 center[2] /= poly->size();
426 for (size_t j = 1; j <= poly->size(); ++j) {
427 gl_draw_triangle(shaderinfo, center, poly->at(j - 1), poly->at(j % poly->size()), true, false, false, 0, mirrored);
428 }
429 }
430 glEnd();
431 }
432 }
433 else {
434 assert(false && "Cannot render object with no dimension");
435 }
436 }
437
438 /*! This is used in throwntogether and CGAL mode
439
440 csgmode is set to CSGMODE_NONE in CGAL mode. In this mode a pure 2D rendering is performed.
441
442 For some reason, this is not used to render edges in Preview mode
443 */
render_edges(const PolySet & ps,csgmode_e csgmode) const444 void Renderer::render_edges(const PolySet &ps, csgmode_e csgmode) const
445 {
446 glDisable(GL_LIGHTING);
447 if (ps.getDimension() == 2) {
448 if (csgmode == Renderer::CSGMODE_NONE) {
449 // Render only outlines
450 for (const Outline2d &o : ps.getPolygon().outlines()) {
451 glBegin(GL_LINE_LOOP);
452 for (const Vector2d &v : o.vertices) {
453 glVertex3d(v[0], v[1], 0);
454 }
455 glEnd();
456 }
457 }
458 else {
459 // Render 2D objects 1mm thick, but differences slightly larger
460 double zbase = 1 + ((csgmode & CSGMODE_DIFFERENCE_FLAG) ? 0.1 : 0);
461
462 for (const Outline2d &o : ps.getPolygon().outlines()) {
463 // Render top+bottom outlines
464 for (double z = -zbase/2; z < zbase; z += zbase) {
465 glBegin(GL_LINE_LOOP);
466 for (const Vector2d &v : o.vertices) {
467 glVertex3d(v[0], v[1], z);
468 }
469 glEnd();
470 }
471 // Render sides
472 glBegin(GL_LINES);
473 for (const Vector2d &v : o.vertices) {
474 glVertex3d(v[0], v[1], -zbase/2);
475 glVertex3d(v[0], v[1], +zbase/2);
476 }
477 glEnd();
478 }
479 }
480 } else if (ps.getDimension() == 3) {
481 for (size_t i = 0; i < ps.polygons.size(); ++i) {
482 const Polygon *poly = &ps.polygons[i];
483 glBegin(GL_LINE_LOOP);
484 for (size_t j = 0; j < poly->size(); ++j) {
485 const Vector3d &p = poly->at(j);
486 glVertex3d(p[0], p[1], p[2]);
487 }
488 glEnd();
489 }
490 }
491 else {
492 assert(false && "Cannot render object with no dimension");
493 }
494 glEnable(GL_LIGHTING);
495 }
496
497
498 #else //NULLGL
gl_draw_triangle(const Renderer::shaderinfo_t * shaderinfo,const Vector3d & p0,const Vector3d & p1,const Vector3d & p2,bool e0,bool e1,bool e2,double z,bool mirrored)499 static void gl_draw_triangle(const Renderer::shaderinfo_t *shaderinfo, const Vector3d &p0, const Vector3d &p1, const Vector3d &p2, bool e0, bool e1, bool e2, double z, bool mirrored) {}
render_surface(const PolySet & ps,csgmode_e csgmode,const Transform3d & m,const shaderinfo_t * shaderinfo) const500 void Renderer::render_surface(const PolySet &ps, csgmode_e csgmode, const Transform3d &m, const shaderinfo_t *shaderinfo) const {}
render_edges(const PolySet & ps,csgmode_e csgmode) const501 void Renderer::render_edges(const PolySet &ps, csgmode_e csgmode) const {}
502 #endif //NULLGL
503