comparison Framework/Scene2D/Internals/OpenGLLinesProgram.cpp @ 592:bbe29efd3d1c

OpenGLLinesProgram
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 26 Apr 2019 12:55:43 +0200
parents
children e3f21a265be5
comparison
equal deleted inserted replaced
591:b66ced2c43d4 592:bbe29efd3d1c
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-2019 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
24 #include <Core/OrthancException.h>
25
26
27 static const unsigned int COMPONENTS_POSITION = 3;
28 static const unsigned int COMPONENTS_MITER = 2;
29
30
31 static const char* VERTEX_SHADER =
32 "attribute vec2 a_miter_direction; \n"
33 "attribute vec4 a_position; \n"
34 "uniform float u_thickness; \n"
35 "uniform mat4 u_matrix; \n"
36 "varying float v_distance; \n"
37 "void main() \n"
38 "{ \n"
39 " v_distance = a_position.z; \n"
40 " gl_Position = u_matrix * vec4(a_position.xy + a_position.z * a_miter_direction * u_thickness, 0, 1); \n"
41 "}";
42
43
44 static const char* FRAGMENT_SHADER =
45 "uniform bool u_antialiasing; \n"
46 "uniform float u_antialiasing_start; \n"
47 "uniform vec3 u_color; \n"
48 "varying float v_distance; \n" // Distance of the point to the segment
49 "void main() \n"
50 "{ \n"
51 " float d = abs(v_distance); \n"
52 " if (!u_antialiasing || \n"
53 " d <= u_antialiasing_start) \n"
54 " gl_FragColor = vec4(u_color, 1); \n"
55 " else if (d >= 1.0) \n"
56 " gl_FragColor = vec4(0, 0, 0, 0); \n"
57 " else \n"
58 " { \n"
59 " float alpha = 1.0 - smoothstep(u_antialiasing_start, 1.0, d); \n"
60 " gl_FragColor = vec4(u_color * alpha, alpha); \n"
61 " } \n"
62 "}";
63
64
65 namespace OrthancStone
66 {
67 namespace Internals
68 {
69 class OpenGLLinesProgram::Data::Segment
70 {
71 private:
72 bool isEmpty_;
73 double x1_;
74 double y1_;
75 double x2_;
76 double y2_;
77 double miterX1_;
78 double miterY1_;
79 double miterX2_;
80 double miterY2_;
81
82 Vector lineAbove_; // In homogeneous coordinates (size = 3)
83 Vector lineBelow_;
84
85 public:
86 Segment(const PolylineSceneLayer::Chain& chain,
87 size_t index1,
88 size_t index2) :
89 isEmpty_(false)
90 {
91 if (index1 >= chain.size() ||
92 index2 >= chain.size())
93 {
94 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
95 }
96 else
97 {
98 const ScenePoint2D& p = chain[index1];
99 const ScenePoint2D& q = chain[index2];
100
101 x1_ = p.GetX();
102 y1_ = p.GetY();
103 x2_ = q.GetX();
104 y2_ = q.GetY();
105
106 const double dx = x2_ - x1_;
107 const double dy = y2_ - y1_;
108 const double norm = sqrt(dx * dx + dy * dy);
109
110 if (LinearAlgebra::IsCloseToZero(norm))
111 {
112 isEmpty_ = true;
113 }
114 else
115 {
116 isEmpty_ = false;
117 const double normalX = -dy / norm;
118 const double normalY = dx / norm;
119
120 miterX1_ = normalX;
121 miterY1_ = normalY;
122 miterX2_ = normalX;
123 miterY2_ = normalY;
124
125 Vector a = LinearAlgebra::CreateVector(x1_ + normalX, y1_ + normalY, 1);
126 Vector b = LinearAlgebra::CreateVector(x2_ + normalX, y2_ + normalY, 1);
127 LinearAlgebra::CrossProduct(lineAbove_, a, b);
128
129 a = LinearAlgebra::CreateVector(x1_ - normalX, y1_ - normalY, 1);
130 b = LinearAlgebra::CreateVector(x2_ - normalX, y2_ - normalY, 1);
131 LinearAlgebra::CrossProduct(lineBelow_, a, b);
132 }
133 }
134 }
135
136 bool IsEmpty() const
137 {
138 return isEmpty_;
139 }
140
141 static double ComputeSignedArea(double x1,
142 double y1,
143 double x2,
144 double y2,
145 double x3,
146 double y3)
147 {
148 // This computes the signed area of a 2D triangle. This
149 // formula is e.g. used in the sorting algorithm of Graham's
150 // scan to compute the convex hull.
151 // https://en.wikipedia.org/wiki/Graham_scan
152 return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
153 }
154
155 static void CreateMiter(Segment& left,
156 Segment& right)
157 {
158 if (!left.IsEmpty() &&
159 !right.IsEmpty())
160 {
161 Vector above, below;
162 LinearAlgebra::CrossProduct(above, left.lineAbove_, right.lineAbove_);
163 LinearAlgebra::CrossProduct(below, left.lineBelow_, right.lineBelow_);
164
165 if (!LinearAlgebra::IsCloseToZero(above[2]) &&
166 !LinearAlgebra::IsCloseToZero(below[2]))
167 {
168 // Back to inhomogeneous 2D coordinates
169 above /= above[2];
170 below /= below[2];
171
172 // Check whether "above" and "below" intersection points
173 // are on the half-plane defined by the endpoints of the
174 // two segments. This is an indicator of whether the angle
175 // is too acute.
176 double s1 = ComputeSignedArea(left.x1_, left.y1_,
177 above[0], above[1],
178 right.x2_, right.y2_);
179 double s2 = ComputeSignedArea(left.x1_, left.y1_,
180 below[0], below[1],
181 right.x2_, right.y2_);
182
183 // The two signed areas must have the same sign
184 if (s1 * s2 >= 0)
185 {
186 left.miterX2_ = above[0] - left.x2_;
187 left.miterY2_ = above[1] - left.y2_;
188
189 right.miterX1_ = left.miterX2_;
190 right.miterY1_ = left.miterY2_;
191 }
192 }
193 }
194 }
195
196 void AddTriangles(std::vector<float>& coords,
197 std::vector<float>& miterDirections)
198 {
199 if (isEmpty_)
200 {
201 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
202 }
203
204 // First triangle
205 coords.push_back(x1_);
206 coords.push_back(y1_);
207 coords.push_back(1);
208 coords.push_back(x2_);
209 coords.push_back(y2_);
210 coords.push_back(-1);
211 coords.push_back(x2_);
212 coords.push_back(y2_);
213 coords.push_back(1);
214
215 miterDirections.push_back(miterX1_);
216 miterDirections.push_back(miterY1_);
217 miterDirections.push_back(miterX2_);
218 miterDirections.push_back(miterY2_);
219 miterDirections.push_back(miterX2_);
220 miterDirections.push_back(miterY2_);
221
222 // Second triangle
223 coords.push_back(x1_);
224 coords.push_back(y1_);
225 coords.push_back(1);
226 coords.push_back(x1_);
227 coords.push_back(y1_);
228 coords.push_back(-1);
229 coords.push_back(x2_);
230 coords.push_back(y2_);
231 coords.push_back(-1);
232
233 miterDirections.push_back(miterX1_);
234 miterDirections.push_back(miterY1_);
235 miterDirections.push_back(miterX1_);
236 miterDirections.push_back(miterY1_);
237 miterDirections.push_back(miterX2_);
238 miterDirections.push_back(miterY2_);
239 }
240 };
241
242
243 OpenGLLinesProgram::Data::Data(OpenGL::IOpenGLContext& context,
244 const PolylineSceneLayer& layer) :
245 context_(context),
246 verticesCount_(0),
247 thickness_(layer.GetThickness()),
248 red_(layer.GetRedAsFloat()),
249 green_(layer.GetGreenAsFloat()),
250 blue_(layer.GetBlueAsFloat())
251 {
252 // High-level reference:
253 // https://mattdesl.svbtle.com/drawing-lines-is-hard
254 // https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader
255
256 size_t countVertices = 0;
257 for (size_t i = 0; i < layer.GetChainsCount(); i++)
258 {
259 size_t countSegments = layer.GetChain(i).size() - 1;
260
261 if (layer.IsClosedChain(i))
262 {
263 countSegments++;
264 }
265
266 // Each segment is made of 2 triangles. One triangle is
267 // defined by 3 points in 2D => 6 vertices per segment.
268 countVertices += countSegments * 2 * 3;
269 }
270
271 std::vector<float> coords, miterDirections;
272 coords.reserve(countVertices * COMPONENTS_POSITION);
273 miterDirections.reserve(countVertices * COMPONENTS_MITER);
274
275 for (size_t i = 0; i < layer.GetChainsCount(); i++)
276 {
277 const PolylineSceneLayer::Chain& chain = layer.GetChain(i);
278
279 if (chain.size() > 1)
280 {
281 std::vector<Segment> segments;
282 for (size_t j = 1; j < chain.size(); j++)
283 {
284 segments.push_back(Segment(chain, j - 1, j));
285 }
286
287 if (layer.IsClosedChain(i))
288 {
289 segments.push_back(Segment(chain, chain.size() - 1, 0));
290 }
291
292 // Try and create nice miters
293 for (size_t j = 1; j < segments.size(); j++)
294 {
295 Segment::CreateMiter(segments[j - 1], segments[j]);
296 }
297
298 if (layer.IsClosedChain(i))
299 {
300 Segment::CreateMiter(segments.back(), segments.front());
301 }
302
303 for (size_t j = 0; j < segments.size(); j++)
304 {
305 if (!segments[j].IsEmpty())
306 {
307 segments[j].AddTriangles(coords, miterDirections);
308 }
309 }
310 }
311 }
312
313 if (!coords.empty())
314 {
315 verticesCount_ = coords.size() / COMPONENTS_POSITION;
316
317 context_.MakeCurrent();
318 glGenBuffers(2, buffers_);
319
320 glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]);
321 glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coords.size(), &coords[0], GL_STATIC_DRAW);
322
323 glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]);
324 glBufferData(GL_ARRAY_BUFFER, sizeof(float) * miterDirections.size(), &miterDirections[0], GL_STATIC_DRAW);
325 }
326 }
327
328
329 OpenGLLinesProgram::Data::~Data()
330 {
331 if (!IsEmpty())
332 {
333 context_.MakeCurrent();
334 glDeleteBuffers(2, buffers_);
335 }
336 }
337
338
339 GLuint OpenGLLinesProgram::Data::GetVerticesBuffer() const
340 {
341 if (IsEmpty())
342 {
343 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
344 }
345 else
346 {
347 return buffers_[0];
348 }
349 }
350
351
352 GLuint OpenGLLinesProgram::Data::GetMiterDirectionsBuffer() const
353 {
354 if (IsEmpty())
355 {
356 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
357 }
358 else
359 {
360 return buffers_[1];
361 }
362 }
363
364
365 OpenGLLinesProgram::OpenGLLinesProgram(OpenGL::IOpenGLContext& context) :
366 context_(context)
367 {
368
369 context_.MakeCurrent();
370
371 program_.reset(new OpenGL::OpenGLProgram);
372 program_->CompileShaders(VERTEX_SHADER, FRAGMENT_SHADER);
373 }
374
375
376 void OpenGLLinesProgram::Apply(const Data& data,
377 const AffineTransform2D& transform,
378 bool antialiasing,
379 bool scaleIndependantThickness)
380 {
381 if (!data.IsEmpty())
382 {
383 context_.MakeCurrent();
384 program_->Use();
385
386 GLint locationPosition = program_->GetAttributeLocation("a_position");
387 GLint locationMiterDirection = program_->GetAttributeLocation("a_miter_direction");
388
389 float m[16];
390 transform.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight());
391
392 glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m);
393 glUniform3f(program_->GetUniformLocation("u_color"),
394 data.GetRed(), data.GetGreen(), data.GetBlue());
395
396 glBindBuffer(GL_ARRAY_BUFFER, data.GetVerticesBuffer());
397 glEnableVertexAttribArray(locationPosition);
398 glVertexAttribPointer(locationPosition, COMPONENTS_POSITION, GL_FLOAT, GL_FALSE, 0, 0);
399
400 glBindBuffer(GL_ARRAY_BUFFER, data.GetMiterDirectionsBuffer());
401 glEnableVertexAttribArray(locationMiterDirection);
402 glVertexAttribPointer(locationMiterDirection, COMPONENTS_MITER, GL_FLOAT, GL_FALSE, 0, 0);
403
404 glUniform1i(program_->GetUniformLocation("u_antialiasing"), (antialiasing ? 1 : 0));
405
406 const double zoom = transform.ComputeZoom();
407 const double thickness = data.GetThickness() / 2.0;
408 const double aliasingBorder = 2.0; // Border for antialiasing ramp, in pixels
409 assert(aliasingBorder > 0); // Prevent division by zero with "t1"
410
411 if (scaleIndependantThickness)
412 {
413 if (antialiasing)
414 {
415 double t1 = std::max(thickness, aliasingBorder);
416 double t0 = std::max(0.0, thickness - aliasingBorder);
417
418 glUniform1f(program_->GetUniformLocation("u_thickness"), t1 / zoom);
419 glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), t0 / t1);
420 }
421 else
422 {
423 glUniform1f(program_->GetUniformLocation("u_thickness"), thickness / zoom);
424 }
425 }
426 else
427 {
428 if (antialiasing)
429 {
430 double t1 = std::max(thickness, aliasingBorder / zoom);
431 double t0 = std::max(0.0, thickness - aliasingBorder / zoom);
432
433 glUniform1f(program_->GetUniformLocation("u_thickness"), t1);
434 glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), t0 / t1);
435 }
436 else
437 {
438 glUniform1f(program_->GetUniformLocation("u_thickness"), thickness);
439 }
440 }
441
442 if (antialiasing)
443 {
444 glEnable(GL_BLEND);
445 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
446 glDrawArrays(GL_TRIANGLES, 0, data.GetVerticesCount());
447 glDisable(GL_BLEND);
448 }
449 else
450 {
451 glDrawArrays(GL_TRIANGLES, 0, data.GetVerticesCount());
452 }
453
454 glDisableVertexAttribArray(locationPosition);
455 glDisableVertexAttribArray(locationMiterDirection);
456 }
457 }
458 }
459 }