comparison OrthancStone/Sources/Scene2D/Internals/OpenGLLinesProgram.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Framework/Scene2D/Internals/OpenGLLinesProgram.cpp@30deba7bc8e2
children 92fca2b3ba3d
comparison
equal deleted inserted replaced
1511:9dfeee74c1e6 1512:244ad1e4e76a
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #include "OpenGLLinesProgram.h"
23 #include "OpenGLShaderVersionDirective.h"
24
25 #include <OrthancException.h>
26
27
28 static const unsigned int COMPONENTS_POSITION = 3;
29 static const unsigned int COMPONENTS_COLOR = 3;
30 static const unsigned int COMPONENTS_MITER = 2;
31
32
33 static const char* VERTEX_SHADER =
34 ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE
35 "attribute vec2 a_miter_direction; \n"
36 "attribute vec4 a_position; \n"
37 "attribute vec3 a_color; \n"
38 "uniform float u_thickness; \n"
39 "uniform mat4 u_matrix; \n"
40 "varying float v_distance; \n"
41 "varying vec3 v_color; \n"
42 "void main() \n"
43 "{ \n"
44 " v_distance = a_position.z; \n"
45 " v_color = a_color; \n"
46 " gl_Position = u_matrix * vec4(a_position.xy + a_position.z * a_miter_direction * u_thickness, 0, 1); \n"
47 "}";
48
49
50 static const char* FRAGMENT_SHADER =
51 ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE
52 "uniform bool u_antialiasing; \n"
53 "uniform float u_antialiasing_start; \n"
54 "varying float v_distance; \n" // Distance of the point to the segment
55 "varying vec3 v_color; \n"
56 "void main() \n"
57 "{ \n"
58 " float d = abs(v_distance); \n"
59 " if (!u_antialiasing || \n"
60 " d <= u_antialiasing_start) \n"
61 " gl_FragColor = vec4(v_color, 1); \n"
62 " else if (d >= 1.0) \n"
63 " gl_FragColor = vec4(0, 0, 0, 0); \n"
64 " else \n"
65 " { \n"
66 " float alpha = 1.0 - smoothstep(u_antialiasing_start, 1.0, d); \n"
67 " gl_FragColor = vec4(v_color * alpha, alpha); \n"
68 " } \n"
69 "}";
70
71
72 namespace OrthancStone
73 {
74 namespace Internals
75 {
76 class OpenGLLinesProgram::Data::Segment
77 {
78 private:
79 bool isEmpty_;
80 double x1_;
81 double y1_;
82 double x2_;
83 double y2_;
84 double miterX1_;
85 double miterY1_;
86 double miterX2_;
87 double miterY2_;
88
89 Vector lineAbove_; // In homogeneous coordinates (size = 3)
90 Vector lineBelow_;
91
92 public:
93 Segment(const PolylineSceneLayer::Chain& chain,
94 size_t index1,
95 size_t index2) :
96 isEmpty_(false)
97 {
98 if (index1 >= chain.size() ||
99 index2 >= chain.size())
100 {
101 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
102 }
103 else
104 {
105 const ScenePoint2D& p = chain[index1];
106 const ScenePoint2D& q = chain[index2];
107
108 x1_ = p.GetX();
109 y1_ = p.GetY();
110 x2_ = q.GetX();
111 y2_ = q.GetY();
112
113 const double dx = x2_ - x1_;
114 const double dy = y2_ - y1_;
115 const double norm = sqrt(dx * dx + dy * dy);
116
117 if (LinearAlgebra::IsCloseToZero(norm))
118 {
119 isEmpty_ = true;
120 }
121 else
122 {
123 isEmpty_ = false;
124 const double normalX = -dy / norm;
125 const double normalY = dx / norm;
126
127 miterX1_ = normalX;
128 miterY1_ = normalY;
129 miterX2_ = normalX;
130 miterY2_ = normalY;
131
132 Vector a = LinearAlgebra::CreateVector(x1_ + normalX, y1_ + normalY, 1);
133 Vector b = LinearAlgebra::CreateVector(x2_ + normalX, y2_ + normalY, 1);
134 LinearAlgebra::CrossProduct(lineAbove_, a, b);
135
136 a = LinearAlgebra::CreateVector(x1_ - normalX, y1_ - normalY, 1);
137 b = LinearAlgebra::CreateVector(x2_ - normalX, y2_ - normalY, 1);
138 LinearAlgebra::CrossProduct(lineBelow_, a, b);
139 }
140 }
141 }
142
143 bool IsEmpty() const
144 {
145 return isEmpty_;
146 }
147
148 static double ComputeSignedArea(double x1,
149 double y1,
150 double x2,
151 double y2,
152 double x3,
153 double y3)
154 {
155 // This computes the signed area of a 2D triangle. This
156 // formula is e.g. used in the sorting algorithm of Graham's
157 // scan to compute the convex hull.
158 // https://en.wikipedia.org/wiki/Graham_scan
159 return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
160 }
161
162 static void CreateMiter(Segment& left,
163 Segment& right)
164 {
165 if (!left.IsEmpty() &&
166 !right.IsEmpty())
167 {
168 Vector above, below;
169 LinearAlgebra::CrossProduct(above, left.lineAbove_, right.lineAbove_);
170 LinearAlgebra::CrossProduct(below, left.lineBelow_, right.lineBelow_);
171
172 if (!LinearAlgebra::IsCloseToZero(above[2]) &&
173 !LinearAlgebra::IsCloseToZero(below[2]))
174 {
175 // Back to inhomogeneous 2D coordinates
176 above /= above[2];
177 below /= below[2];
178
179 // Check whether "above" and "below" intersection points
180 // are on the half-plane defined by the endpoints of the
181 // two segments. This is an indicator of whether the angle
182 // is too acute.
183 double s1 = ComputeSignedArea(left.x1_, left.y1_,
184 above[0], above[1],
185 right.x2_, right.y2_);
186 double s2 = ComputeSignedArea(left.x1_, left.y1_,
187 below[0], below[1],
188 right.x2_, right.y2_);
189
190 // The two signed areas must have the same sign
191 if (s1 * s2 >= 0)
192 {
193 left.miterX2_ = above[0] - left.x2_;
194 left.miterY2_ = above[1] - left.y2_;
195
196 right.miterX1_ = left.miterX2_;
197 right.miterY1_ = left.miterY2_;
198 }
199 }
200 }
201 }
202
203 void AddTriangles(std::vector<float>& coords,
204 std::vector<float>& miterDirections,
205 std::vector<float>& colors,
206 const Color& color)
207 {
208 if (isEmpty_)
209 {
210 LOG(ERROR) << "OpenGLLinesProgram -- AddTriangles: (isEmpty_)";
211 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
212 }
213
214 // First triangle
215 coords.push_back(static_cast<float>(x1_));
216 coords.push_back(static_cast<float>(y1_));
217 coords.push_back(static_cast<float>(1));
218 coords.push_back(static_cast<float>(x2_));
219 coords.push_back(static_cast<float>(y2_));
220 coords.push_back(static_cast<float>(-1));
221 coords.push_back(static_cast<float>(x2_));
222 coords.push_back(static_cast<float>(y2_));
223 coords.push_back(static_cast<float>(1));
224
225 miterDirections.push_back(static_cast<float>(miterX1_));
226 miterDirections.push_back(static_cast<float>(miterY1_));
227 miterDirections.push_back(static_cast<float>(miterX2_));
228 miterDirections.push_back(static_cast<float>(miterY2_));
229 miterDirections.push_back(static_cast<float>(miterX2_));
230 miterDirections.push_back(static_cast<float>(miterY2_));
231
232 // Second triangle
233 coords.push_back(static_cast<float>(x1_));
234 coords.push_back(static_cast<float>(y1_));
235 coords.push_back(static_cast<float>(1));
236 coords.push_back(static_cast<float>(x1_));
237 coords.push_back(static_cast<float>(y1_));
238 coords.push_back(static_cast<float>(-1));
239 coords.push_back(static_cast<float>(x2_));
240 coords.push_back(static_cast<float>(y2_));
241 coords.push_back(static_cast<float>(-1));
242
243 miterDirections.push_back(static_cast<float>(miterX1_));
244 miterDirections.push_back(static_cast<float>(miterY1_));
245 miterDirections.push_back(static_cast<float>(miterX1_));
246 miterDirections.push_back(static_cast<float>(miterY1_));
247 miterDirections.push_back(static_cast<float>(miterX2_));
248 miterDirections.push_back(static_cast<float>(miterY2_));
249
250 // Add the colors of the 2 triangles (leading to 2 * 3 values)
251 for (unsigned int i = 0; i < 6; i++)
252 {
253 colors.push_back(color.GetRedAsFloat());
254 colors.push_back(color.GetGreenAsFloat());
255 colors.push_back(color.GetBlueAsFloat());
256 }
257 }
258 };
259
260
261 OpenGLLinesProgram::Data::Data(OpenGL::IOpenGLContext& context,
262 const PolylineSceneLayer& layer) :
263 context_(context),
264 verticesCount_(0),
265 thickness_(static_cast<float>(layer.GetThickness()))
266 {
267 if (!context_.IsContextLost())
268 {
269 // High-level reference:
270 // https://mattdesl.svbtle.com/drawing-lines-is-hard
271 // https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader
272
273 size_t countVertices = 0;
274 for (size_t i = 0; i < layer.GetChainsCount(); i++)
275 {
276 size_t countSegments = layer.GetChain(i).size() - 1;
277
278 if (layer.IsClosedChain(i))
279 {
280 countSegments++;
281 }
282
283 // Each segment is made of 2 triangles. One triangle is
284 // defined by 3 points in 2D => 6 vertices per segment.
285 countVertices += countSegments * 2 * 3;
286 }
287
288 std::vector<float> coords, colors, miterDirections;
289 coords.reserve(countVertices * COMPONENTS_POSITION);
290 colors.reserve(countVertices * COMPONENTS_COLOR);
291 miterDirections.reserve(countVertices * COMPONENTS_MITER);
292
293 for (size_t i = 0; i < layer.GetChainsCount(); i++)
294 {
295 const PolylineSceneLayer::Chain& chain = layer.GetChain(i);
296
297 if (chain.size() > 1)
298 {
299 std::vector<Segment> segments;
300 for (size_t j = 1; j < chain.size(); j++)
301 {
302 segments.push_back(Segment(chain, j - 1, j));
303 }
304
305 if (layer.IsClosedChain(i))
306 {
307 segments.push_back(Segment(chain, chain.size() - 1, 0));
308 }
309
310 // Try and create nice miters
311 for (size_t j = 1; j < segments.size(); j++)
312 {
313 Segment::CreateMiter(segments[j - 1], segments[j]);
314 }
315
316 if (layer.IsClosedChain(i))
317 {
318 Segment::CreateMiter(segments.back(), segments.front());
319 }
320
321 for (size_t j = 0; j < segments.size(); j++)
322 {
323 if (!segments[j].IsEmpty())
324 {
325 segments[j].AddTriangles(coords, miterDirections, colors, layer.GetColor(i));
326 }
327 }
328 }
329 }
330
331 assert(coords.size() == colors.size());
332
333 if (!coords.empty())
334 {
335 verticesCount_ = coords.size() / COMPONENTS_POSITION;
336
337 context_.MakeCurrent();
338 glGenBuffers(3, buffers_);
339
340 glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]);
341 glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coords.size(), &coords[0], GL_STATIC_DRAW);
342
343 glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]);
344 glBufferData(GL_ARRAY_BUFFER, sizeof(float) * miterDirections.size(), &miterDirections[0], GL_STATIC_DRAW);
345
346 glBindBuffer(GL_ARRAY_BUFFER, buffers_[2]);
347 glBufferData(GL_ARRAY_BUFFER, sizeof(float) * colors.size(), &colors[0], GL_STATIC_DRAW);
348 }
349 }
350 }
351
352
353 OpenGLLinesProgram::Data::~Data()
354 {
355 if (!context_.IsContextLost() && !IsEmpty())
356 {
357 context_.MakeCurrent();
358 ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT("About to call glDeleteBuffers");
359 glDeleteBuffers(3, buffers_);
360 }
361 }
362
363 GLuint OpenGLLinesProgram::Data::GetVerticesBuffer() const
364 {
365 if (IsEmpty())
366 {
367 LOG(ERROR) << "OpenGLLinesProgram::Data::GetVerticesBuffer(): (IsEmpty())";
368 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
369 }
370 else
371 {
372 return buffers_[0];
373 }
374 }
375
376
377 GLuint OpenGLLinesProgram::Data::GetMiterDirectionsBuffer() const
378 {
379 if (IsEmpty())
380 {
381 LOG(ERROR) << "OpenGLLinesProgram::Data::GetMiterDirectionsBuffer(): (IsEmpty())";
382 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
383 }
384 else
385 {
386 return buffers_[1];
387 }
388 }
389
390
391 GLuint OpenGLLinesProgram::Data::GetColorsBuffer() const
392 {
393 if (IsEmpty())
394 {
395 LOG(ERROR) << "OpenGLLinesProgram::Data::GetColorsBuffer(): (IsEmpty())";
396 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
397 }
398 else
399 {
400 return buffers_[2];
401 }
402 }
403
404
405 OpenGLLinesProgram::OpenGLLinesProgram(OpenGL::IOpenGLContext& context) :
406 context_(context)
407 {
408 if (!context_.IsContextLost())
409 {
410 context_.MakeCurrent();
411 program_.reset(new OpenGL::OpenGLProgram(context_));
412 program_->CompileShaders(VERTEX_SHADER, FRAGMENT_SHADER);
413 }
414 }
415
416 void OpenGLLinesProgram::Apply(const Data& data,
417 const AffineTransform2D& transform,
418 bool antialiasing,
419 bool scaleIndependantThickness)
420 {
421 if (!context_.IsContextLost() && !data.IsEmpty())
422 {
423 context_.MakeCurrent();
424 program_->Use();
425
426 GLint locationPosition = program_->GetAttributeLocation("a_position");
427 GLint locationMiterDirection = program_->GetAttributeLocation("a_miter_direction");
428 GLint locationColor = program_->GetAttributeLocation("a_color");
429
430 float m[16];
431 transform.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight());
432
433 glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m);
434
435 glBindBuffer(GL_ARRAY_BUFFER, data.GetVerticesBuffer());
436 glEnableVertexAttribArray(locationPosition);
437 glVertexAttribPointer(locationPosition, COMPONENTS_POSITION, GL_FLOAT, GL_FALSE, 0, 0);
438
439 glBindBuffer(GL_ARRAY_BUFFER, data.GetMiterDirectionsBuffer());
440 glEnableVertexAttribArray(locationMiterDirection);
441 glVertexAttribPointer(locationMiterDirection, COMPONENTS_MITER, GL_FLOAT, GL_FALSE, 0, 0);
442
443 glBindBuffer(GL_ARRAY_BUFFER, data.GetColorsBuffer());
444 glEnableVertexAttribArray(locationColor);
445 glVertexAttribPointer(locationColor, COMPONENTS_COLOR, GL_FLOAT, GL_FALSE, 0, 0);
446
447 glUniform1i(program_->GetUniformLocation("u_antialiasing"), (antialiasing ? 1 : 0));
448
449 const double zoom = transform.ComputeZoom();
450 const double thickness = data.GetThickness() / 2.0;
451 const double aliasingBorder = 2.0; // Border for antialiasing ramp, in pixels
452 assert(aliasingBorder > 0); // Prevent division by zero with "t1"
453
454 if (scaleIndependantThickness)
455 {
456 if (antialiasing)
457 {
458 double t1 = std::max(thickness, aliasingBorder);
459 double t0 = std::max(0.0, thickness - aliasingBorder);
460
461 glUniform1f(program_->GetUniformLocation("u_thickness"),
462 static_cast<GLfloat>(t1 / zoom));
463 glUniform1f(program_->GetUniformLocation("u_antialiasing_start"),
464 static_cast<GLfloat>(t0 / t1));
465 }
466 else
467 {
468 glUniform1f(program_->GetUniformLocation("u_thickness"),
469 static_cast<GLfloat>(thickness / zoom));
470 }
471 }
472 else
473 {
474 if (antialiasing)
475 {
476 double t1 = std::max(thickness, aliasingBorder / zoom);
477 double t0 = std::max(0.0, thickness - aliasingBorder / zoom);
478
479 glUniform1f(program_->GetUniformLocation("u_thickness"),
480 static_cast<GLfloat>(t1));
481 glUniform1f(program_->GetUniformLocation("u_antialiasing_start"),
482 static_cast<GLfloat>(t0 / t1));
483 }
484 else
485 {
486 glUniform1f(program_->GetUniformLocation("u_thickness"),
487 static_cast<GLfloat>(thickness));
488 }
489 }
490
491 if (antialiasing)
492 {
493 glEnable(GL_BLEND);
494 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
495 glDrawArrays(GL_TRIANGLES, 0,
496 static_cast<GLsizei>(data.GetVerticesCount()));
497 glDisable(GL_BLEND);
498 }
499 else
500 {
501 glDrawArrays(GL_TRIANGLES, 0,
502 static_cast<GLsizei>(data.GetVerticesCount()));
503 }
504
505 glDisableVertexAttribArray(locationPosition);
506 glDisableVertexAttribArray(locationMiterDirection);
507 glDisableVertexAttribArray(locationColor);
508 }
509 }
510 }
511 }