Mercurial > hg > orthanc-stone
view Framework/Scene2D/Internals/OpenGLLinesProgram.cpp @ 1327:4f8db2d202c8 broker
OrthancSeriesProgressiveLoader now has two modes that
can be selected at object creation :
- progressive (will first load jpeg50, then jpeg90 then PAM)
- non-progressive (will directly load PAM (uncompressed))
Please note that the slice loading order remains dynamic
and depending upon the slice that the client code wishes
to extract from the volume.
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 25 Mar 2020 14:34:27 +0100 |
parents | 2d8ab34c8c91 |
children | 30deba7bc8e2 |
line wrap: on
line source
/** * Stone of Orthanc * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2020 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "OpenGLLinesProgram.h" #include "OpenGLShaderVersionDirective.h" #include <Core/OrthancException.h> static const unsigned int COMPONENTS_POSITION = 3; static const unsigned int COMPONENTS_COLOR = 3; static const unsigned int COMPONENTS_MITER = 2; static const char* VERTEX_SHADER = ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE "attribute vec2 a_miter_direction; \n" "attribute vec4 a_position; \n" "attribute vec3 a_color; \n" "uniform float u_thickness; \n" "uniform mat4 u_matrix; \n" "varying float v_distance; \n" "varying vec3 v_color; \n" "void main() \n" "{ \n" " v_distance = a_position.z; \n" " v_color = a_color; \n" " gl_Position = u_matrix * vec4(a_position.xy + a_position.z * a_miter_direction * u_thickness, 0, 1); \n" "}"; static const char* FRAGMENT_SHADER = ORTHANC_STONE_OPENGL_SHADER_VERSION_DIRECTIVE "uniform bool u_antialiasing; \n" "uniform float u_antialiasing_start; \n" "varying float v_distance; \n" // Distance of the point to the segment "varying vec3 v_color; \n" "void main() \n" "{ \n" " float d = abs(v_distance); \n" " if (!u_antialiasing || \n" " d <= u_antialiasing_start) \n" " gl_FragColor = vec4(v_color, 1); \n" " else if (d >= 1.0) \n" " gl_FragColor = vec4(0, 0, 0, 0); \n" " else \n" " { \n" " float alpha = 1.0 - smoothstep(u_antialiasing_start, 1.0, d); \n" " gl_FragColor = vec4(v_color * alpha, alpha); \n" " } \n" "}"; namespace OrthancStone { namespace Internals { class OpenGLLinesProgram::Data::Segment { private: bool isEmpty_; double x1_; double y1_; double x2_; double y2_; double miterX1_; double miterY1_; double miterX2_; double miterY2_; Vector lineAbove_; // In homogeneous coordinates (size = 3) Vector lineBelow_; public: Segment(const PolylineSceneLayer::Chain& chain, size_t index1, size_t index2) : isEmpty_(false) { if (index1 >= chain.size() || index2 >= chain.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } else { const ScenePoint2D& p = chain[index1]; const ScenePoint2D& q = chain[index2]; x1_ = p.GetX(); y1_ = p.GetY(); x2_ = q.GetX(); y2_ = q.GetY(); const double dx = x2_ - x1_; const double dy = y2_ - y1_; const double norm = sqrt(dx * dx + dy * dy); if (LinearAlgebra::IsCloseToZero(norm)) { isEmpty_ = true; } else { isEmpty_ = false; const double normalX = -dy / norm; const double normalY = dx / norm; miterX1_ = normalX; miterY1_ = normalY; miterX2_ = normalX; miterY2_ = normalY; Vector a = LinearAlgebra::CreateVector(x1_ + normalX, y1_ + normalY, 1); Vector b = LinearAlgebra::CreateVector(x2_ + normalX, y2_ + normalY, 1); LinearAlgebra::CrossProduct(lineAbove_, a, b); a = LinearAlgebra::CreateVector(x1_ - normalX, y1_ - normalY, 1); b = LinearAlgebra::CreateVector(x2_ - normalX, y2_ - normalY, 1); LinearAlgebra::CrossProduct(lineBelow_, a, b); } } } bool IsEmpty() const { return isEmpty_; } static double ComputeSignedArea(double x1, double y1, double x2, double y2, double x3, double y3) { // This computes the signed area of a 2D triangle. This // formula is e.g. used in the sorting algorithm of Graham's // scan to compute the convex hull. // https://en.wikipedia.org/wiki/Graham_scan return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1); } static void CreateMiter(Segment& left, Segment& right) { if (!left.IsEmpty() && !right.IsEmpty()) { Vector above, below; LinearAlgebra::CrossProduct(above, left.lineAbove_, right.lineAbove_); LinearAlgebra::CrossProduct(below, left.lineBelow_, right.lineBelow_); if (!LinearAlgebra::IsCloseToZero(above[2]) && !LinearAlgebra::IsCloseToZero(below[2])) { // Back to inhomogeneous 2D coordinates above /= above[2]; below /= below[2]; // Check whether "above" and "below" intersection points // are on the half-plane defined by the endpoints of the // two segments. This is an indicator of whether the angle // is too acute. double s1 = ComputeSignedArea(left.x1_, left.y1_, above[0], above[1], right.x2_, right.y2_); double s2 = ComputeSignedArea(left.x1_, left.y1_, below[0], below[1], right.x2_, right.y2_); // The two signed areas must have the same sign if (s1 * s2 >= 0) { left.miterX2_ = above[0] - left.x2_; left.miterY2_ = above[1] - left.y2_; right.miterX1_ = left.miterX2_; right.miterY1_ = left.miterY2_; } } } } void AddTriangles(std::vector<float>& coords, std::vector<float>& miterDirections, std::vector<float>& colors, const Color& color) { if (isEmpty_) { LOG(ERROR) << "OpenGLLinesProgram -- AddTriangles: (isEmpty_)"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } // First triangle coords.push_back(static_cast<float>(x1_)); coords.push_back(static_cast<float>(y1_)); coords.push_back(static_cast<float>(1)); coords.push_back(static_cast<float>(x2_)); coords.push_back(static_cast<float>(y2_)); coords.push_back(static_cast<float>(-1)); coords.push_back(static_cast<float>(x2_)); coords.push_back(static_cast<float>(y2_)); coords.push_back(static_cast<float>(1)); miterDirections.push_back(static_cast<float>(miterX1_)); miterDirections.push_back(static_cast<float>(miterY1_)); miterDirections.push_back(static_cast<float>(miterX2_)); miterDirections.push_back(static_cast<float>(miterY2_)); miterDirections.push_back(static_cast<float>(miterX2_)); miterDirections.push_back(static_cast<float>(miterY2_)); // Second triangle coords.push_back(static_cast<float>(x1_)); coords.push_back(static_cast<float>(y1_)); coords.push_back(static_cast<float>(1)); coords.push_back(static_cast<float>(x1_)); coords.push_back(static_cast<float>(y1_)); coords.push_back(static_cast<float>(-1)); coords.push_back(static_cast<float>(x2_)); coords.push_back(static_cast<float>(y2_)); coords.push_back(static_cast<float>(-1)); miterDirections.push_back(static_cast<float>(miterX1_)); miterDirections.push_back(static_cast<float>(miterY1_)); miterDirections.push_back(static_cast<float>(miterX1_)); miterDirections.push_back(static_cast<float>(miterY1_)); miterDirections.push_back(static_cast<float>(miterX2_)); miterDirections.push_back(static_cast<float>(miterY2_)); // Add the colors of the 2 triangles (leading to 2 * 3 values) for (unsigned int i = 0; i < 6; i++) { colors.push_back(color.GetRedAsFloat()); colors.push_back(color.GetGreenAsFloat()); colors.push_back(color.GetBlueAsFloat()); } } }; OpenGLLinesProgram::Data::Data(OpenGL::IOpenGLContext& context, const PolylineSceneLayer& layer) : context_(context), verticesCount_(0), thickness_(static_cast<float>(layer.GetThickness())) { if (!context_.IsContextLost()) { // High-level reference: // https://mattdesl.svbtle.com/drawing-lines-is-hard // https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader size_t countVertices = 0; for (size_t i = 0; i < layer.GetChainsCount(); i++) { size_t countSegments = layer.GetChain(i).size() - 1; if (layer.IsClosedChain(i)) { countSegments++; } // Each segment is made of 2 triangles. One triangle is // defined by 3 points in 2D => 6 vertices per segment. countVertices += countSegments * 2 * 3; } std::vector<float> coords, colors, miterDirections; coords.reserve(countVertices * COMPONENTS_POSITION); colors.reserve(countVertices * COMPONENTS_COLOR); miterDirections.reserve(countVertices * COMPONENTS_MITER); for (size_t i = 0; i < layer.GetChainsCount(); i++) { const PolylineSceneLayer::Chain& chain = layer.GetChain(i); if (chain.size() > 1) { std::vector<Segment> segments; for (size_t j = 1; j < chain.size(); j++) { segments.push_back(Segment(chain, j - 1, j)); } if (layer.IsClosedChain(i)) { segments.push_back(Segment(chain, chain.size() - 1, 0)); } // Try and create nice miters for (size_t j = 1; j < segments.size(); j++) { Segment::CreateMiter(segments[j - 1], segments[j]); } if (layer.IsClosedChain(i)) { Segment::CreateMiter(segments.back(), segments.front()); } for (size_t j = 0; j < segments.size(); j++) { if (!segments[j].IsEmpty()) { segments[j].AddTriangles(coords, miterDirections, colors, layer.GetColor(i)); } } } } assert(coords.size() == colors.size()); if (!coords.empty()) { verticesCount_ = coords.size() / COMPONENTS_POSITION; context_.MakeCurrent(); glGenBuffers(3, buffers_); glBindBuffer(GL_ARRAY_BUFFER, buffers_[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * coords.size(), &coords[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, buffers_[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * miterDirections.size(), &miterDirections[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, buffers_[2]); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * colors.size(), &colors[0], GL_STATIC_DRAW); } } } OpenGLLinesProgram::Data::~Data() { if (!context_.IsContextLost() && !IsEmpty()) { context_.MakeCurrent(); ORTHANC_OPENGL_TRACE_CURRENT_CONTEXT("About to call glDeleteBuffers"); glDeleteBuffers(3, buffers_); } } GLuint OpenGLLinesProgram::Data::GetVerticesBuffer() const { if (IsEmpty()) { LOG(ERROR) << "OpenGLLinesProgram::Data::GetVerticesBuffer(): (IsEmpty())"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } else { return buffers_[0]; } } GLuint OpenGLLinesProgram::Data::GetMiterDirectionsBuffer() const { if (IsEmpty()) { LOG(ERROR) << "OpenGLLinesProgram::Data::GetMiterDirectionsBuffer(): (IsEmpty())"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } else { return buffers_[1]; } } GLuint OpenGLLinesProgram::Data::GetColorsBuffer() const { if (IsEmpty()) { LOG(ERROR) << "OpenGLLinesProgram::Data::GetColorsBuffer(): (IsEmpty())"; throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } else { return buffers_[2]; } } OpenGLLinesProgram::OpenGLLinesProgram(OpenGL::IOpenGLContext& context) : context_(context) { if (!context_.IsContextLost()) { context_.MakeCurrent(); program_.reset(new OpenGL::OpenGLProgram(context_)); program_->CompileShaders(VERTEX_SHADER, FRAGMENT_SHADER); } } void OpenGLLinesProgram::Apply(const Data& data, const AffineTransform2D& transform, bool antialiasing, bool scaleIndependantThickness) { if (!context_.IsContextLost() && !data.IsEmpty()) { context_.MakeCurrent(); program_->Use(); GLint locationPosition = program_->GetAttributeLocation("a_position"); GLint locationMiterDirection = program_->GetAttributeLocation("a_miter_direction"); GLint locationColor = program_->GetAttributeLocation("a_color"); float m[16]; transform.ConvertToOpenGLMatrix(m, context_.GetCanvasWidth(), context_.GetCanvasHeight()); glUniformMatrix4fv(program_->GetUniformLocation("u_matrix"), 1, GL_FALSE, m); glBindBuffer(GL_ARRAY_BUFFER, data.GetVerticesBuffer()); glEnableVertexAttribArray(locationPosition); glVertexAttribPointer(locationPosition, COMPONENTS_POSITION, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, data.GetMiterDirectionsBuffer()); glEnableVertexAttribArray(locationMiterDirection); glVertexAttribPointer(locationMiterDirection, COMPONENTS_MITER, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, data.GetColorsBuffer()); glEnableVertexAttribArray(locationColor); glVertexAttribPointer(locationColor, COMPONENTS_COLOR, GL_FLOAT, GL_FALSE, 0, 0); glUniform1i(program_->GetUniformLocation("u_antialiasing"), (antialiasing ? 1 : 0)); const double zoom = transform.ComputeZoom(); const double thickness = data.GetThickness() / 2.0; const double aliasingBorder = 2.0; // Border for antialiasing ramp, in pixels assert(aliasingBorder > 0); // Prevent division by zero with "t1" if (scaleIndependantThickness) { if (antialiasing) { double t1 = std::max(thickness, aliasingBorder); double t0 = std::max(0.0, thickness - aliasingBorder); glUniform1f(program_->GetUniformLocation("u_thickness"), static_cast<GLfloat>(t1 / zoom)); glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), static_cast<GLfloat>(t0 / t1)); } else { glUniform1f(program_->GetUniformLocation("u_thickness"), static_cast<GLfloat>(thickness / zoom)); } } else { if (antialiasing) { double t1 = std::max(thickness, aliasingBorder / zoom); double t0 = std::max(0.0, thickness - aliasingBorder / zoom); glUniform1f(program_->GetUniformLocation("u_thickness"), static_cast<GLfloat>(t1)); glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), static_cast<GLfloat>(t0 / t1)); } else { glUniform1f(program_->GetUniformLocation("u_thickness"), static_cast<GLfloat>(thickness)); } } if (antialiasing) { glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(data.GetVerticesCount())); glDisable(GL_BLEND); } else { glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(data.GetVerticesCount())); } glDisableVertexAttribArray(locationPosition); glDisableVertexAttribArray(locationMiterDirection); glDisableVertexAttribArray(locationColor); } } } }