/*========================================================================= Program: Visualization Toolkit Module: vtkOpenGLContextDevice2D.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkOpenGLContextDevice2D.h" #include "vtkAbstractContextBufferId.h" #include "vtkBrush.h" #include "vtkContext2D.h" #include "vtkFloatArray.h" #include "vtkImageData.h" #include "vtkImageResize.h" #include "vtkMath.h" #include "vtkMatrix3x3.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkOpenGLError.h" #include "vtkOpenGLGL2PSHelper.h" #include "vtkOpenGLHelper.h" #include "vtkOpenGLIndexBufferObject.h" #include "vtkOpenGLRenderWindow.h" #include "vtkOpenGLRenderer.h" #include "vtkOpenGLShaderCache.h" #include "vtkOpenGLState.h" #include "vtkOpenGLTexture.h" #include "vtkOpenGLVertexArrayObject.h" #include "vtkOpenGLVertexBufferObject.h" #include "vtkPath.h" #include "vtkPen.h" #include "vtkPointData.h" #include "vtkPoints2D.h" #include "vtkPolyData.h" #include "vtkRect.h" #include "vtkShaderProgram.h" #include "vtkSmartPointer.h" #include "vtkTextProperty.h" #include "vtkTextRenderer.h" #include "vtkTexture.h" #include "vtkTextureUnitManager.h" #include "vtkTransform.h" #include "vtkTransformFeedback.h" #include "vtkVector.h" #include "vtkViewport.h" #include "vtkWindow.h" #include "vtkObjectFactory.h" #include "vtkOpenGLContextDevice2DPrivate.h" #include #include #include #include #define BUFFER_OFFSET(i) (reinterpret_cast(i)) namespace { void copyColors(std::vector& newColors, unsigned char* colors, int nc) { for (int j = 0; j < nc; j++) { newColors.push_back(colors[j]); } } const char* myVertShader = "in vec2 vertexMC;\n" "uniform mat4 WCDCMatrix;\n" "uniform mat4 MCWCMatrix;\n" "#ifdef haveColors\n" "in vec4 vertexScalar;\n" "out vec4 vertexColor;\n" "#endif\n" "#ifdef haveTCoords\n" "in vec2 tcoordMC;\n" "out vec2 tcoord;\n" "#endif\n" "#ifdef haveLines\n" "in vec2 tcoordMC;\n" "out float ldistance;\n" "#endif\n" "void main() {\n" "#ifdef haveColors\n" "vertexColor = vertexScalar;\n" "#endif\n" "#ifdef haveTCoords\n" "tcoord = tcoordMC;\n" "#endif\n" "#ifdef haveLines\n" "ldistance = tcoordMC.x;\n" "#endif\n" "vec4 vertex = vec4(vertexMC.xy, 0.0, 1.0);\n" "gl_Position = vertex*MCWCMatrix*WCDCMatrix; }\n"; const char* myFragShader = "//VTK::Output::Dec\n" "#ifdef haveColors\n" "in vec4 vertexColor;\n" "#else\n" "uniform vec4 vertexColor;\n" "#endif\n" "#ifdef haveTCoords\n" "in vec2 tcoord;\n" "uniform sampler2D texture1;\n" "#endif\n" "#ifdef haveLines\n" "in float ldistance;\n" "uniform int stipple;\n" "#endif\n" "void main() {\n" "#ifdef haveLines\n" "if ((0x01 << int(mod(ldistance,16.0)) & stipple) == 0) { discard; }\n" "#endif\n" "#ifdef haveTCoords\n" " gl_FragData[0] = texture2D(texture1, tcoord);\n" "#else\n" " gl_FragData[0] = vertexColor;\n" "#endif\n" "}\n"; //------------------------------------------------------------------------------ // Returns true when rendering the GL2PS background raster image. Vectorizable // primitives should not be drawn during these passes. bool SkipDraw() { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); return gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Background; } //------------------------------------------------------------------------------ // Releases the current shader program if it is inconsistent with the GL2PS // capture state. Returns the current OpenGLGL2PSHelper instance if one exists. vtkOpenGLGL2PSHelper* PrepProgramForGL2PS(vtkOpenGLHelper& helper) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { // Always recreate the program when doing GL2PS capture. if (helper.Program) { helper.ReleaseGraphicsResources(nullptr); } } else { // If there is a feedback transform capturer set on the current shader // program and we're not capturing, recreate the program. if (helper.Program && helper.Program->GetTransformFeedback()) { helper.ReleaseGraphicsResources(nullptr); } } return gl2ps; } //------------------------------------------------------------------------------ // Call before glDraw* commands to ensure that vertices are properly captured // for GL2PS export. void PreDraw(vtkOpenGLHelper& helper, int drawMode, size_t numVerts) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture && helper.Program) { if (vtkTransformFeedback* tfc = helper.Program->GetTransformFeedback()) { tfc->SetNumberOfVertices(drawMode, numVerts); tfc->BindBuffer(); } } } //------------------------------------------------------------------------------ // Call after glDraw* commands to ensure that vertices are properly captured // for GL2PS export. void PostDraw(vtkOpenGLHelper& helper, vtkRenderer* ren, unsigned char col[4]) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture && helper.Program) { if (vtkTransformFeedback* tfc = helper.Program->GetTransformFeedback()) { tfc->ReadBuffer(); tfc->ReleaseGraphicsResources(); gl2ps->ProcessTransformFeedback(tfc, ren, col); tfc->ReleaseBufferData(); } } } //------------------------------------------------------------------------------ // Returns true if the startAngle and stopAngle (as used in the ellipse drawing // functions) describe a full circle. inline bool IsFullCircle(float startAngle, float stopAngle) { // A small number practical for rendering purposes. const float TOL = 1e-5f; return std::fabs(stopAngle - startAngle) + TOL >= 360.f; } } // end anon namespace //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkOpenGLContextDevice2D); //------------------------------------------------------------------------------ vtkOpenGLContextDevice2D::vtkOpenGLContextDevice2D() { this->Renderer = nullptr; this->InRender = false; this->Storage = new vtkOpenGLContextDevice2D::Private; this->PolyDataImpl = new vtkOpenGLContextDevice2D::CellArrayHelper(this); this->RenderWindow = nullptr; this->MaximumMarkerCacheSize = 20; this->ProjectionMatrix = vtkTransform::New(); this->ModelMatrix = vtkTransform::New(); this->VBO = new vtkOpenGLHelper; this->VCBO = new vtkOpenGLHelper; this->LinesBO = new vtkOpenGLHelper; this->LinesCBO = new vtkOpenGLHelper; this->VTBO = new vtkOpenGLHelper; this->SBO = new vtkOpenGLHelper; this->SCBO = new vtkOpenGLHelper; this->LinePattern = 0xFFFF; } //------------------------------------------------------------------------------ vtkOpenGLContextDevice2D::~vtkOpenGLContextDevice2D() { delete this->VBO; this->VBO = nullptr; delete this->VCBO; this->VCBO = nullptr; delete this->LinesBO; this->LinesBO = nullptr; delete this->LinesCBO; this->LinesCBO = nullptr; delete this->SBO; this->SBO = nullptr; delete this->SCBO; this->SCBO = nullptr; delete this->VTBO; this->VTBO = nullptr; while (!this->MarkerCache.empty()) { this->MarkerCache.back().Value->Delete(); this->MarkerCache.pop_back(); } this->ProjectionMatrix->Delete(); this->ModelMatrix->Delete(); delete this->Storage; delete this->PolyDataImpl; } vtkMatrix4x4* vtkOpenGLContextDevice2D::GetProjectionMatrix() { return this->ProjectionMatrix->GetMatrix(); } vtkMatrix4x4* vtkOpenGLContextDevice2D::GetModelMatrix() { return this->ModelMatrix->GetMatrix(); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::Begin(vtkViewport* viewport) { vtkOpenGLClearErrorMacro(); // Need the actual pixel size of the viewport - ask OpenGL. GLint vp[4]; glGetIntegerv(GL_VIEWPORT, vp); this->Storage->Offset.Set(static_cast(vp[0]), static_cast(vp[1])); this->Storage->Dim.Set(static_cast(vp[2]), static_cast(vp[3])); // push a 2D matrix on the stack this->ProjectionMatrix->Push(); this->ProjectionMatrix->Identity(); this->PushMatrix(); this->ModelMatrix->Identity(); double offset = 0.5; double xmin = offset; double xmax = vp[2] + offset - 1.0; double ymin = offset; double ymax = vp[3] + offset - 1.0; double znear = -2000; double zfar = 2000; double matrix[4][4]; vtkMatrix4x4::Identity(*matrix); matrix[0][0] = 2 / (xmax - xmin); matrix[1][1] = 2 / (ymax - ymin); matrix[2][2] = -2 / (zfar - znear); matrix[0][3] = -(xmin + xmax) / (xmax - xmin); matrix[1][3] = -(ymin + ymax) / (ymax - ymin); matrix[2][3] = -(znear + zfar) / (zfar - znear); this->ProjectionMatrix->SetMatrix(*matrix); // Store the previous state before changing it this->Renderer = vtkRenderer::SafeDownCast(viewport); this->RenderWindow = vtkOpenGLRenderWindow::SafeDownCast(this->Renderer->GetRenderWindow()); vtkOpenGLState* ostate = this->RenderWindow->GetState(); this->Storage->SaveGLState(ostate); ostate->vtkglDisable(GL_DEPTH_TEST); ostate->vtkglEnable(GL_BLEND); this->RenderWindow->GetShaderCache()->ReleaseCurrentShader(); // Enable simple line smoothing if multisampling is on. #ifdef GL_LINE_SMOOTH if (this->Renderer->GetRenderWindow()->GetMultiSamples()) { this->RenderWindow->GetState()->vtkglEnable(GL_LINE_SMOOTH); } #endif this->InRender = true; vtkOpenGLCheckErrorMacro("failed after Begin"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::End() { if (!this->InRender) { return; } this->ProjectionMatrix->Pop(); this->PopMatrix(); vtkOpenGLClearErrorMacro(); // Restore the GL state that we changed vtkOpenGLState* ostate = this->RenderWindow->GetState(); this->Storage->RestoreGLState(ostate); // Disable simple line smoothing if multisampling is on. #ifdef GL_LINE_SMOOTH if (this->Renderer->GetRenderWindow()->GetMultiSamples()) { this->RenderWindow->GetState()->vtkglDisable(GL_LINE_SMOOTH); } #endif this->PolyDataImpl->HandleEndFrame(); this->RenderWindow = nullptr; this->InRender = false; vtkOpenGLCheckErrorMacro("failed after End"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::BufferIdModeBegin(vtkAbstractContextBufferId* bufferId) { assert("pre: not_yet" && !this->GetBufferIdMode()); assert("pre: bufferId_exists" && bufferId != nullptr); vtkOpenGLClearErrorMacro(); this->BufferId = bufferId; // Save OpenGL state. vtkOpenGLState* ostate = this->RenderWindow->GetState(); this->Storage->SaveGLState(ostate, true); int lowerLeft[2]; int usize, vsize; this->Renderer->GetTiledSizeAndOrigin(&usize, &vsize, lowerLeft, lowerLeft + 1); // push a 2D matrix on the stack this->ProjectionMatrix->Push(); this->ProjectionMatrix->Identity(); this->PushMatrix(); this->ModelMatrix->Identity(); double xmin = 0.5; double xmax = usize + 0.5; double ymin = 0.5; double ymax = vsize + 0.5; double znear = -1; double zfar = 1; double matrix[4][4]; vtkMatrix4x4::Identity(*matrix); matrix[0][0] = 2 / (xmax - xmin); matrix[1][1] = 2 / (ymax - ymin); matrix[2][2] = -2 / (zfar - znear); matrix[0][3] = -(xmin + xmax) / (xmax - xmin); matrix[1][3] = -(ymin + ymax) / (ymax - ymin); matrix[2][3] = -(znear + zfar) / (zfar - znear); this->ProjectionMatrix->SetMatrix(*matrix); ostate->vtkglDrawBuffer(GL_BACK_LEFT); ostate->vtkglClearColor(0.0, 0.0, 0.0, 0.0); // id=0 means no hit, just background ostate->vtkglClear(GL_COLOR_BUFFER_BIT); ostate->vtkglDisable(GL_STENCIL_TEST); ostate->vtkglDisable(GL_DEPTH_TEST); ostate->vtkglDisable(GL_BLEND); vtkOpenGLCheckErrorMacro("failed after BufferIdModeBegin"); assert("post: started" && this->GetBufferIdMode()); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::BufferIdModeEnd() { assert("pre: started" && this->GetBufferIdMode()); vtkOpenGLClearErrorMacro(); // Assume the renderer has been set previously during rendering (sse Begin()) int lowerLeft[2]; int usize, vsize; this->Renderer->GetTiledSizeAndOrigin(&usize, &vsize, lowerLeft, lowerLeft + 1); this->BufferId->SetValues(lowerLeft[0], lowerLeft[1]); this->ProjectionMatrix->Pop(); this->PopMatrix(); this->Storage->RestoreGLState(this->RenderWindow->GetState(), true); this->BufferId = nullptr; vtkOpenGLCheckErrorMacro("failed after BufferIdModeEnd"); assert("post: done" && !this->GetBufferIdMode()); } void vtkOpenGLContextDevice2D::SetMatrices(vtkShaderProgram* prog) { prog->SetUniformMatrix("WCDCMatrix", this->ProjectionMatrix->GetMatrix()); prog->SetUniformMatrix("MCWCMatrix", this->ModelMatrix->GetMatrix()); } void vtkOpenGLContextDevice2D::BuildVBO( vtkOpenGLHelper* cellBO, float* f, int nv, unsigned char* colors, int nc, float* tcoords) { int stride = 2; int cOffset = 0; int tOffset = 0; if (colors) { cOffset = stride; stride++; } if (tcoords) { tOffset = stride; stride += 2; } std::vector va; va.resize(nv * stride); vtkFourByteUnion c; for (int i = 0; i < nv; i++) { va[i * stride] = f[i * 2]; va[i * stride + 1] = f[i * 2 + 1]; if (colors) { c.c[0] = colors[nc * i]; c.c[1] = colors[nc * i + 1]; c.c[2] = colors[nc * i + 2]; if (nc == 4) { c.c[3] = colors[nc * i + 3]; } else { c.c[3] = 255; } va[i * stride + cOffset] = c.f; } if (tcoords) { va[i * stride + tOffset] = tcoords[i * 2]; va[i * stride + tOffset + 1] = tcoords[i * 2 + 1]; } } // upload the data cellBO->IBO->Upload(va, vtkOpenGLBufferObject::ArrayBuffer); cellBO->VAO->ShaderProgramChanged(); cellBO->VAO->Bind(); if (!cellBO->VAO->AddAttributeArray( cellBO->Program, cellBO->IBO, "vertexMC", 0, sizeof(float) * stride, VTK_FLOAT, 2, false)) { vtkErrorMacro(<< "Error setting vertexMC in shader VAO."); } if (colors) { if (!cellBO->VAO->AddAttributeArray(cellBO->Program, cellBO->IBO, "vertexScalar", sizeof(float) * cOffset, sizeof(float) * stride, VTK_UNSIGNED_CHAR, 4, true)) { vtkErrorMacro(<< "Error setting vertexScalar in shader VAO."); } } if (tcoords) { if (!cellBO->VAO->AddAttributeArray(cellBO->Program, cellBO->IBO, "tcoordMC", sizeof(float) * tOffset, sizeof(float) * stride, VTK_FLOAT, 2, false)) { vtkErrorMacro(<< "Error setting tcoordMC in shader VAO."); } } cellBO->VAO->Bind(); } void vtkOpenGLContextDevice2D::ReadyVBOProgram() { vtkOpenGLGL2PSHelper* gl2ps = PrepProgramForGL2PS(*this->VBO); if (!this->VBO->Program) { vtkTransformFeedback* tf = nullptr; if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { tf = vtkTransformFeedback::New(); tf->AddVarying(vtkTransformFeedback::Vertex_ClipCoordinate_F, "gl_Position"); } std::string vs = "//VTK::System::Dec\n"; vs += myVertShader; std::string fs = "//VTK::System::Dec\n"; fs += myFragShader; this->VBO->Program = this->RenderWindow->GetShaderCache()->ReadyShaderProgram(vs.c_str(), fs.c_str(), "", tf); if (tf) { tf->Delete(); tf = nullptr; } } else { this->RenderWindow->GetShaderCache()->ReadyShaderProgram(this->VBO->Program); } } void vtkOpenGLContextDevice2D::ReadyVCBOProgram() { vtkOpenGLGL2PSHelper* gl2ps = PrepProgramForGL2PS(*this->VCBO); if (!this->VCBO->Program) { vtkTransformFeedback* tf = nullptr; if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { tf = vtkTransformFeedback::New(); tf->AddVarying(vtkTransformFeedback::Vertex_ClipCoordinate_F, "gl_Position"); tf->AddVarying(vtkTransformFeedback::Color_RGBA_F, "vertexColor"); } std::string vs = "//VTK::System::Dec\n#define haveColors\n"; vs += myVertShader; std::string fs = "//VTK::System::Dec\n#define haveColors\n"; fs += myFragShader; this->VCBO->Program = this->RenderWindow->GetShaderCache()->ReadyShaderProgram(vs.c_str(), fs.c_str(), "", tf); if (tf) { tf->Delete(); tf = nullptr; } } else { this->RenderWindow->GetShaderCache()->ReadyShaderProgram(this->VCBO->Program); } } void vtkOpenGLContextDevice2D::ReadyLinesBOProgram() { vtkOpenGLGL2PSHelper* gl2ps = PrepProgramForGL2PS(*this->LinesBO); if (!this->LinesBO->Program) { vtkTransformFeedback* tf = nullptr; if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { tf = vtkTransformFeedback::New(); tf->AddVarying(vtkTransformFeedback::Vertex_ClipCoordinate_F, "gl_Position"); } std::string vs = "//VTK::System::Dec\n#define haveLines\n"; vs += myVertShader; std::string fs = "//VTK::System::Dec\n#define haveLines\n"; fs += myFragShader; this->LinesBO->Program = this->RenderWindow->GetShaderCache()->ReadyShaderProgram(vs.c_str(), fs.c_str(), "", tf); if (tf) { tf->Delete(); tf = nullptr; } } else { this->RenderWindow->GetShaderCache()->ReadyShaderProgram(this->LinesBO->Program); } } void vtkOpenGLContextDevice2D::ReadyLinesCBOProgram() { vtkOpenGLGL2PSHelper* gl2ps = PrepProgramForGL2PS(*this->LinesCBO); if (!this->LinesCBO->Program) { vtkTransformFeedback* tf = nullptr; if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { tf = vtkTransformFeedback::New(); tf->AddVarying(vtkTransformFeedback::Vertex_ClipCoordinate_F, "gl_Position"); tf->AddVarying(vtkTransformFeedback::Color_RGBA_F, "vertexColor"); } std::string vs = "//VTK::System::Dec\n#define haveColors\n#define haveLines\n"; vs += myVertShader; std::string fs = "//VTK::System::Dec\n#define haveColors\n#define haveLines\n"; fs += myFragShader; this->LinesCBO->Program = this->RenderWindow->GetShaderCache()->ReadyShaderProgram(vs.c_str(), fs.c_str(), "", tf); if (tf) { tf->Delete(); tf = nullptr; } } else { this->RenderWindow->GetShaderCache()->ReadyShaderProgram(this->LinesCBO->Program); } } void vtkOpenGLContextDevice2D::ReadyVTBOProgram() { if (!this->VTBO->Program) { std::string vs = "//VTK::System::Dec\n#define haveTCoords\n"; vs += myVertShader; std::string fs = "//VTK::System::Dec\n#define haveTCoords\n"; fs += myFragShader; this->VTBO->Program = this->RenderWindow->GetShaderCache()->ReadyShaderProgram(vs.c_str(), fs.c_str(), ""); } else { this->RenderWindow->GetShaderCache()->ReadyShaderProgram(this->VTBO->Program); } } void vtkOpenGLContextDevice2D::ReadySBOProgram() { if (!this->SBO->Program) { this->SBO->Program = this->RenderWindow->GetShaderCache()->ReadyShaderProgram( // vertex shader "//VTK::System::Dec\n" "in vec2 vertexMC;\n" "uniform mat4 WCDCMatrix;\n" "uniform mat4 MCWCMatrix;\n" "void main() {\n" "vec4 vertex = vec4(vertexMC.xy, 0.0, 1.0);\n" "gl_Position = vertex*MCWCMatrix*WCDCMatrix; }\n", // fragment shader "//VTK::System::Dec\n" "//VTK::Output::Dec\n" "uniform vec4 vertexColor;\n" "uniform sampler2D texture1;\n" "void main() { gl_FragData[0] = vertexColor*texture2D(texture1, gl_PointCoord); }", // geometry shader ""); } else { this->RenderWindow->GetShaderCache()->ReadyShaderProgram(this->SBO->Program); } } void vtkOpenGLContextDevice2D::ReadySCBOProgram() { if (!this->SCBO->Program) { this->SCBO->Program = this->RenderWindow->GetShaderCache()->ReadyShaderProgram( // vertex shader "//VTK::System::Dec\n" "in vec2 vertexMC;\n" "in vec4 vertexScalar;\n" "uniform mat4 WCDCMatrix;\n" "uniform mat4 MCWCMatrix;\n" "out vec4 vertexColor;\n" "void main() {\n" "vec4 vertex = vec4(vertexMC.xy, 0.0, 1.0);\n" "vertexColor = vertexScalar;\n" "gl_Position = vertex*MCWCMatrix*WCDCMatrix; }\n", // fragment shader "//VTK::System::Dec\n" "//VTK::Output::Dec\n" "in vec4 vertexColor;\n" "uniform sampler2D texture1;\n" "void main() { gl_FragData[0] = vertexColor*texture2D(texture1, gl_PointCoord); }", // geometry shader ""); } else { this->RenderWindow->GetShaderCache()->ReadyShaderProgram(this->SCBO->Program); } } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawPoly(float* f, int n, unsigned char* colors, int nc) { assert("f must be non-null" && f != nullptr); assert("n must be greater than 0" && n > 0); if (SkipDraw()) { return; } if (this->Pen->GetLineType() == vtkPen::NO_PEN) { return; } // Skip transparent elements. if (!colors && this->Pen->GetColorObject().GetAlpha() == 0) { return; } vtkOpenGLClearErrorMacro(); this->SetLineType(this->Pen->GetLineType()); vtkOpenGLHelper* cbo = nullptr; if (colors) { this->ReadyLinesCBOProgram(); cbo = this->LinesCBO; } else { this->ReadyLinesBOProgram(); cbo = this->LinesBO; if (cbo->Program) { cbo->Program->SetUniform4uc("vertexColor", this->Pen->GetColor()); } } if (!cbo->Program) { return; } cbo->Program->SetUniformi("stipple", this->LinePattern); this->SetMatrices(cbo->Program); // for line stipple we need to compute the scaled // cumulative linear distance double* scale = this->ModelMatrix->GetScale(); std::vector distances; distances.resize(n * 2); float totDist = 0.0; distances[0] = 0.0; for (int i = 1; i < n; i++) { float xDel = scale[0] * (f[i * 2] - f[i * 2 - 2]); float yDel = scale[1] * (f[i * 2 + 1] - f[i * 2 - 1]); // discarding infinite coordinates totDist += (std::abs(yDel) != std::numeric_limits::infinity() && std::abs(xDel) != std::numeric_limits::infinity()) ? sqrt(xDel * xDel + yDel * yDel) : 0; distances[i * 2] = totDist; } // For GL2PS captures, use the path that draws lines instead of triangles -- // GL2PS can handle stipples and linewidths just fine. vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (this->Pen->GetWidth() > 1.0 && !(gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture)) { // convert to triangles and draw, this is because // OpenGL no longer supports wide lines directly float hwidth = this->Pen->GetWidth() / 2.0; std::vector newVerts; std::vector newColors; std::vector newDistances; newDistances.resize((n - 1) * 12); for (int i = 0; i < n - 1; i++) { // for each line segment draw two triangles // start by computing the direction vtkVector2f dir( (f[i * 2 + 2] - f[i * 2]) * scale[0], (f[i * 2 + 3] - f[i * 2 + 1]) * scale[1]); vtkVector2f norm(-dir.GetY(), dir.GetX()); norm.Normalize(); norm.SetX(hwidth * norm.GetX() / scale[0]); norm.SetY(hwidth * norm.GetY() / scale[1]); newVerts.push_back(f[i * 2] + norm.GetX()); newVerts.push_back(f[i * 2 + 1] + norm.GetY()); newVerts.push_back(f[i * 2] - norm.GetX()); newVerts.push_back(f[i * 2 + 1] - norm.GetY()); newVerts.push_back(f[i * 2 + 2] - norm.GetX()); newVerts.push_back(f[i * 2 + 3] - norm.GetY()); newVerts.push_back(f[i * 2] + norm.GetX()); newVerts.push_back(f[i * 2 + 1] + norm.GetY()); newVerts.push_back(f[i * 2 + 2] - norm.GetX()); newVerts.push_back(f[i * 2 + 3] - norm.GetY()); newVerts.push_back(f[i * 2 + 2] + norm.GetX()); newVerts.push_back(f[i * 2 + 3] + norm.GetY()); if (colors) { copyColors(newColors, colors + i * nc, nc); copyColors(newColors, colors + i * nc, nc); copyColors(newColors, colors + (i + 1) * nc, nc); copyColors(newColors, colors + i * nc, nc); copyColors(newColors, colors + (i + 1) * nc, nc); copyColors(newColors, colors + (i + 1) * nc, nc); } newDistances[i * 12] = distances[i * 2]; newDistances[i * 12 + 2] = distances[i * 2]; newDistances[i * 12 + 4] = distances[i * 2 + 2]; newDistances[i * 12 + 6] = distances[i * 2]; newDistances[i * 12 + 8] = distances[i * 2 + 2]; newDistances[i * 12 + 10] = distances[i * 2 + 2]; } this->BuildVBO(cbo, &(newVerts[0]), static_cast(newVerts.size() / 2), colors ? &(newColors[0]) : nullptr, nc, &(newDistances[0])); PreDraw(*cbo, GL_TRIANGLES, newVerts.size() / 2); glDrawArrays(GL_TRIANGLES, 0, static_cast(newVerts.size() / 2)); PostDraw(*cbo, this->Renderer, this->Pen->GetColor()); } else { this->SetLineWidth(this->Pen->GetWidth()); this->BuildVBO(cbo, f, n, colors, nc, &(distances[0])); PreDraw(*cbo, GL_LINE_STRIP, n); glDrawArrays(GL_LINE_STRIP, 0, n); PostDraw(*cbo, this->Renderer, this->Pen->GetColor()); this->SetLineWidth(1.0); } vtkOpenGLCheckErrorMacro("failed after DrawPoly"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawLines(float* f, int n, unsigned char* colors, int nc) { assert("f must be non-null" && f != nullptr); assert("n must be greater than 0" && n > 0); if (SkipDraw()) { return; } if (this->Pen->GetLineType() == vtkPen::NO_PEN) { return; } // Skip transparent elements. if (!colors && this->Pen->GetColorObject().GetAlpha() == 0) { return; } vtkOpenGLClearErrorMacro(); this->SetLineType(this->Pen->GetLineType()); vtkOpenGLHelper* cbo = nullptr; if (colors) { this->ReadyLinesCBOProgram(); cbo = this->LinesCBO; } else { this->ReadyLinesBOProgram(); cbo = this->LinesBO; if (!cbo->Program) { return; } cbo->Program->SetUniform4uc("vertexColor", this->Pen->GetColor()); } if (!cbo->Program) { return; } cbo->Program->SetUniformi("stipple", this->LinePattern); this->SetMatrices(cbo->Program); // for line stipple we need to compute the scaled // cumulative linear distance double* scale = this->ModelMatrix->GetScale(); std::vector distances; distances.resize(n * 2); float totDist = 0.0; distances[0] = 0.0; for (int i = 1; i < n; i++) { float xDel = scale[0] * (f[i * 2] - f[i * 2 - 2]); float yDel = scale[1] * (f[i * 2 + 1] - f[i * 2 - 1]); totDist += sqrt(xDel * xDel + yDel * yDel); distances[i * 2] = totDist; } if (this->Pen->GetWidth() > 1.0) { // convert to triangles and draw, this is because // OpenGL no longer supports wide lines directly float hwidth = this->Pen->GetWidth() / 2.0; std::vector newVerts; std::vector newColors; std::vector newDistances; newDistances.resize((n / 2) * 12); for (int i = 0; i < n - 1; i += 2) { // for each line segment draw two triangles // start by computing the direction vtkVector2f dir( (f[i * 2 + 2] - f[i * 2]) * scale[0], (f[i * 2 + 3] - f[i * 2 + 1]) * scale[1]); vtkVector2f norm(-dir.GetY(), dir.GetX()); norm.Normalize(); norm.SetX(hwidth * norm.GetX() / scale[0]); norm.SetY(hwidth * norm.GetY() / scale[1]); newVerts.push_back(f[i * 2] + norm.GetX()); newVerts.push_back(f[i * 2 + 1] + norm.GetY()); newVerts.push_back(f[i * 2] - norm.GetX()); newVerts.push_back(f[i * 2 + 1] - norm.GetY()); newVerts.push_back(f[i * 2 + 2] - norm.GetX()); newVerts.push_back(f[i * 2 + 3] - norm.GetY()); newVerts.push_back(f[i * 2] + norm.GetX()); newVerts.push_back(f[i * 2 + 1] + norm.GetY()); newVerts.push_back(f[i * 2 + 2] - norm.GetX()); newVerts.push_back(f[i * 2 + 3] - norm.GetY()); newVerts.push_back(f[i * 2 + 2] + norm.GetX()); newVerts.push_back(f[i * 2 + 3] + norm.GetY()); if (colors) { copyColors(newColors, colors + i * nc, nc); copyColors(newColors, colors + i * nc, nc); copyColors(newColors, colors + (i + 1) * nc, nc); copyColors(newColors, colors + i * nc, nc); copyColors(newColors, colors + (i + 1) * nc, nc); copyColors(newColors, colors + (i + 1) * nc, nc); } newDistances[i * 6] = distances[i * 2]; newDistances[i * 6 + 2] = distances[i * 2]; newDistances[i * 6 + 4] = distances[i * 2 + 2]; newDistances[i * 6 + 6] = distances[i * 2]; newDistances[i * 6 + 8] = distances[i * 2 + 2]; newDistances[i * 6 + 10] = distances[i * 2 + 2]; } this->BuildVBO(cbo, &(newVerts[0]), static_cast(newVerts.size() / 2), colors ? &(newColors[0]) : nullptr, nc, &(newDistances[0])); PreDraw(*cbo, GL_TRIANGLES, newVerts.size() / 2); glDrawArrays(GL_TRIANGLES, 0, static_cast(newVerts.size() / 2)); PostDraw(*cbo, this->Renderer, this->Pen->GetColor()); } else { this->SetLineWidth(this->Pen->GetWidth()); this->BuildVBO(cbo, f, n, colors, nc, &(distances[0])); PreDraw(*cbo, GL_LINES, n); glDrawArrays(GL_LINES, 0, n); PostDraw(*cbo, this->Renderer, this->Pen->GetColor()); this->SetLineWidth(1.0); } vtkOpenGLCheckErrorMacro("failed after DrawLines"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawPoints(float* f, int n, unsigned char* c, int nc) { if (SkipDraw()) { return; } // Skip transparent elements. if (!c && this->Pen->GetColorObject().GetAlpha() == 0) { return; } vtkOpenGLClearErrorMacro(); vtkOpenGLHelper* cbo = nullptr; if (c) { this->ReadyVCBOProgram(); cbo = this->VCBO; if (!cbo->Program) { return; } } else { this->ReadyVBOProgram(); cbo = this->VBO; if (!cbo->Program) { return; } cbo->Program->SetUniform4uc("vertexColor", this->Pen->GetColor()); } this->SetPointSize(this->Pen->GetWidth()); this->BuildVBO(cbo, f, n, c, nc, nullptr); this->SetMatrices(cbo->Program); PreDraw(*cbo, GL_POINTS, n); glDrawArrays(GL_POINTS, 0, n); PostDraw(*cbo, this->Renderer, this->Pen->GetColor()); vtkOpenGLCheckErrorMacro("failed after DrawPoints"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawPointSprites( vtkImageData* sprite, float* points, int n, unsigned char* colors, int nc_comps) { // // Draw these to the background -- we don't currently export them to GL2PS. // if (SkipDraw()) // { // return; // } vtkOpenGLClearErrorMacro(); if (points && n > 0) { this->SetPointSize(this->Pen->GetWidth()); vtkOpenGLHelper* cbo = nullptr; if (colors) { this->ReadySCBOProgram(); cbo = this->SCBO; if (!cbo->Program) { return; } } else { this->ReadySBOProgram(); cbo = this->SBO; if (!cbo->Program) { return; } cbo->Program->SetUniform4uc("vertexColor", this->Pen->GetColor()); } this->BuildVBO(cbo, points, n, colors, nc_comps, nullptr); this->SetMatrices(cbo->Program); if (sprite) { if (!this->Storage->SpriteTexture) { this->Storage->SpriteTexture = vtkTexture::New(); } int properties = this->Brush->GetTextureProperties(); this->Storage->SpriteTexture->SetInputData(sprite); this->Storage->SpriteTexture->SetRepeat(properties & vtkContextDevice2D::Repeat); this->Storage->SpriteTexture->SetInterpolate(properties & vtkContextDevice2D::Linear); this->Storage->SpriteTexture->Render(this->Renderer); int tunit = vtkOpenGLTexture::SafeDownCast(this->Storage->SpriteTexture)->GetTextureUnit(); cbo->Program->SetUniformi("texture1", tunit); } // We can actually use point sprites here if (this->RenderWindow->IsPointSpriteBugPresent()) { glEnable(GL_POINT_SPRITE); glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE); } glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_LOWER_LEFT); glDrawArrays(GL_POINTS, 0, n); if (this->RenderWindow->IsPointSpriteBugPresent()) { glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_FALSE); glDisable(GL_POINT_SPRITE); } if (sprite) { this->Storage->SpriteTexture->PostRender(this->Renderer); } } else { vtkWarningMacro(<< "Points supplied without a valid image or pointer."); } vtkOpenGLCheckErrorMacro("failed after DrawPointSprites"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawMarkers( int shape, bool highlight, float* points, int n, unsigned char* colors, int nc_comps) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps) { switch (gl2ps->GetActiveState()) { case vtkOpenGLGL2PSHelper::Capture: this->DrawMarkersGL2PS(shape, highlight, points, n, colors, nc_comps); return; case vtkOpenGLGL2PSHelper::Background: return; // Do nothing. case vtkOpenGLGL2PSHelper::Inactive: break; // Render as normal. } } // Get a point sprite for the shape vtkImageData* sprite = this->GetMarker(shape, this->Pen->GetWidth(), highlight); this->DrawPointSprites(sprite, points, n, colors, nc_comps); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawQuad(float* f, int n) { if (SkipDraw()) { return; } if (!f || n <= 0) { vtkWarningMacro(<< "Points supplied that were not of type float."); return; } // convert quads to triangles std::vector tverts; int numTVerts = 6 * n / 4; tverts.resize(numTVerts * 2); int offset[6] = { 0, 1, 2, 0, 2, 3 }; for (int i = 0; i < numTVerts; i++) { int index = 2 * (4 * (i / 6) + offset[i % 6]); tverts[i * 2] = f[index]; tverts[i * 2 + 1] = f[index + 1]; } this->CoreDrawTriangles(tverts); } void vtkOpenGLContextDevice2D::CoreDrawTriangles( std::vector& tverts, unsigned char* colors, int numComp) { if (SkipDraw()) { return; } vtkOpenGLClearErrorMacro(); float* texCoord = nullptr; vtkOpenGLHelper* cbo = nullptr; if (this->Brush->GetTexture()) { this->ReadyVTBOProgram(); cbo = this->VTBO; if (!cbo->Program) { return; } this->SetTexture(this->Brush->GetTexture(), this->Brush->GetTextureProperties()); this->Storage->Texture->Render(this->Renderer); texCoord = this->Storage->TexCoords(&(tverts[0]), static_cast(tverts.size() / 2)); int tunit = vtkOpenGLTexture::SafeDownCast(this->Storage->Texture)->GetTextureUnit(); cbo->Program->SetUniformi("texture1", tunit); } else if (colors && numComp > 0) { this->ReadyVCBOProgram(); cbo = this->VCBO; } else { // Skip transparent elements. if (this->Brush->GetColorObject().GetAlpha() == 0) { return; } this->ReadyVBOProgram(); cbo = this->VBO; } if (!cbo->Program) { return; } cbo->Program->SetUniform4uc("vertexColor", this->Brush->GetColor()); this->BuildVBO(cbo, &(tverts[0]), static_cast(tverts.size() / 2), colors, numComp, texCoord); this->SetMatrices(cbo->Program); PreDraw(*cbo, GL_TRIANGLES, tverts.size() / 2); glDrawArrays(GL_TRIANGLES, 0, static_cast(tverts.size() / 2)); PostDraw(*cbo, this->Renderer, this->Brush->GetColor()); if (this->Storage->Texture) { this->Storage->Texture->PostRender(this->Renderer); delete[] texCoord; } vtkOpenGLCheckErrorMacro("failed after DrawQuad"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawQuadStrip(float* f, int n) { if (SkipDraw()) { return; } if (!f || n <= 0) { vtkWarningMacro(<< "Points supplied that were not of type float."); return; } // convert quad strips to triangles std::vector tverts; int numTVerts = 3 * (n - 2); tverts.resize(numTVerts * 2); int offset[6] = { 0, 1, 3, 0, 3, 2 }; for (int i = 0; i < numTVerts; i++) { int index = 2 * (2 * (i / 6) + offset[i % 6]); tverts[i * 2] = f[index]; tverts[i * 2 + 1] = f[index + 1]; } this->CoreDrawTriangles(tverts); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawPolygon(float* f, int n) { if (SkipDraw()) { return; } if (!f || n <= 0) { vtkWarningMacro(<< "Points supplied that were not of type float."); return; } // convert polygon to triangles std::vector tverts; int numTVerts = 3 * (n - 2); tverts.reserve(numTVerts * 2); for (int i = 0; i < n - 2; i++) { tverts.push_back(f[0]); tverts.push_back(f[1]); tverts.push_back(f[i * 2 + 2]); tverts.push_back(f[i * 2 + 3]); tverts.push_back(f[i * 2 + 4]); tverts.push_back(f[i * 2 + 5]); } this->CoreDrawTriangles(tverts); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawColoredPolygon( float* f, int n, unsigned char* colors, int nc_comps) { if (SkipDraw()) { return; } if (!f || n <= 0) { vtkWarningMacro(<< "Points supplied that were not of type float."); return; } // convert polygon to triangles int numTVerts = 3 * (n - 2); std::vector tverts; tverts.reserve(numTVerts * 2); std::vector tcolors; if (colors) { tcolors.resize(numTVerts * nc_comps); } std::vector::iterator colIt = tcolors.begin(); for (int i = 0; i < n - 2; i++) { tverts.push_back(f[0]); tverts.push_back(f[1]); tverts.push_back(f[i * 2 + 2]); tverts.push_back(f[i * 2 + 3]); tverts.push_back(f[i * 2 + 4]); tverts.push_back(f[i * 2 + 5]); if (colors) { std::copy(colors, colors + nc_comps, colIt); colIt += nc_comps; std::copy(colors + ((i + 1) * nc_comps), colors + ((i + 3) * nc_comps), colIt); colIt += 2 * nc_comps; } } this->CoreDrawTriangles(tverts, colors ? tcolors.data() : nullptr, nc_comps); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawEllipseWedge(float x, float y, float outRx, float outRy, float inRx, float inRy, float startAngle, float stopAngle) { assert("pre: positive_outRx" && outRx >= 0.0f); assert("pre: positive_outRy" && outRy >= 0.0f); assert("pre: positive_inRx" && inRx >= 0.0f); assert("pre: positive_inRy" && inRy >= 0.0f); assert("pre: ordered_rx" && inRx <= outRx); assert("pre: ordered_ry" && inRy <= outRy); if (SkipDraw()) { return; } if (outRy == 0.0f && outRx == 0.0f) { // we make sure maxRadius will never be null. return; } // If the 'wedge' is actually a full circle, gl2ps can just insert a circle // instead of using a polygonal approximation. if (IsFullCircle(startAngle, stopAngle)) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { this->DrawWedgeGL2PS(x, y, outRx, outRy, inRx, inRy); return; } } int iterations = this->GetNumberOfArcIterations(outRx, outRy, startAngle, stopAngle); // step in radians. double step = vtkMath::RadiansFromDegrees(stopAngle - startAngle) / (iterations); // step have to be lesser or equal to maxStep computed inside // GetNumberOfIterations() double rstart = vtkMath::RadiansFromDegrees(startAngle); // the A vertices (0,2,4,..) are on the inner side // the B vertices (1,3,5,..) are on the outer side // (A and B vertices terms come from triangle strip definition in // OpenGL spec) // we are iterating counterclockwise // convert polygon to triangles std::vector tverts; int numTVerts = 6 * iterations; tverts.resize(numTVerts * 2); int offset[6] = { 0, 1, 3, 0, 3, 2 }; for (int i = 0; i < numTVerts; i++) { int index = i / 6 + offset[i % 6] / 2; double radiusX = (offset[i % 6] % 2) ? outRx : inRx; double radiusY = (offset[i % 6] % 2) ? outRy : inRy; double a = rstart + index * step; tverts.push_back(radiusX * cos(a) + x); tverts.push_back(radiusY * sin(a) + y); } this->CoreDrawTriangles(tverts); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawEllipticArc( float x, float y, float rX, float rY, float startAngle, float stopAngle) { assert("pre: positive_rX" && rX >= 0); assert("pre: positive_rY" && rY >= 0); if (SkipDraw()) { return; } if (rX == 0.0f && rY == 0.0f) { // we make sure maxRadius will never be null. return; } // If the 'arc' is actually a full circle, gl2ps can just insert a circle // instead of using a polygonal approximation. if (IsFullCircle(startAngle, stopAngle)) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { this->DrawCircleGL2PS(x, y, rX, rY); return; } } vtkOpenGLClearErrorMacro(); int iterations = this->GetNumberOfArcIterations(rX, rY, startAngle, stopAngle); float* p = new float[2 * (iterations + 1)]; // step in radians. double step = vtkMath::RadiansFromDegrees(stopAngle - startAngle) / (iterations); // step have to be lesser or equal to maxStep computed inside // GetNumberOfIterations() double rstart = vtkMath::RadiansFromDegrees(startAngle); // we are iterating counterclockwise for (int i = 0; i <= iterations; ++i) { double a = rstart + i * step; p[2 * i] = rX * cos(a) + x; p[2 * i + 1] = rY * sin(a) + y; } this->DrawPolygon(p, iterations + 1); this->DrawPoly(p, iterations + 1); delete[] p; vtkOpenGLCheckErrorMacro("failed after DrawEllipseArc"); } //------------------------------------------------------------------------------ int vtkOpenGLContextDevice2D::GetNumberOfArcIterations( float rX, float rY, float startAngle, float stopAngle) { assert("pre: positive_rX" && rX >= 0.0f); assert("pre: positive_rY" && rY >= 0.0f); assert("pre: not_both_null" && (rX > 0.0 || rY > 0.0)); // 1.0: pixel precision. 0.5 (subpixel precision, useful with multisampling) double error = 4.0; // experience shows 4.0 is visually enough. // The tessellation is the most visible on the biggest radius. double maxRadius; if (rX >= rY) { maxRadius = rX; } else { maxRadius = rY; } if (error > maxRadius) { // to make sure the argument of asin() is in a valid range. error = maxRadius; } // Angle of a sector so that its chord is `error' pixels. // This is will be our maximum angle step. double maxStep = 2.0 * asin(error / (2.0 * maxRadius)); // ceil because we want to make sure we don't underestimate the number of // iterations by 1. return static_cast(ceil(vtkMath::RadiansFromDegrees(stopAngle - startAngle) / maxStep)); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::ComputeStringBounds(const vtkStdString& string, float bounds[4]) { this->ComputeStringBoundsInternal(string, bounds); bounds[0] = 0.f; bounds[1] = 0.f; } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::ComputeJustifiedStringBounds(const char* string, float bounds[4]) { this->ComputeStringBoundsInternal(string, bounds); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawString(float* point, const vtkStdString& string) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps) { switch (gl2ps->GetActiveState()) { case vtkOpenGLGL2PSHelper::Capture: { float tx = point[0]; float ty = point[1]; this->TransformPoint(tx, ty); double x[3] = { tx, ty, 0. }; gl2ps->DrawString(string, this->TextProp, x, 0., this->Renderer); return; } case vtkOpenGLGL2PSHelper::Background: return; // Do nothing. case vtkOpenGLGL2PSHelper::Inactive: break; // Render as normal. } } vtkTextRenderer* tren = vtkTextRenderer::GetInstance(); if (!tren) { vtkErrorMacro("No text renderer available. Link to vtkRenderingFreeType " "to get the default implementation."); return; } vtkOpenGLClearErrorMacro(); double* mv = this->ModelMatrix->GetMatrix()->Element[0]; float xScale = mv[0]; float yScale = mv[5]; float p[] = { std::floor(point[0] * xScale) / xScale, std::floor(point[1] * yScale) / yScale }; // TODO this currently ignores vtkContextScene::ScaleTiles. Not sure how to // get at that from here, but this is better than ignoring scaling altogether. // TODO Also, FreeType supports anisotropic DPI. Might be needed if the // tileScale isn't homogeneous, but we'll need to update the textrenderer API // and see if MPL/mathtext can support it. int tileScale[2]; this->RenderWindow->GetTileScale(tileScale); int dpi = this->RenderWindow->GetDPI() * std::max(tileScale[0], tileScale[1]); // Cache rendered text strings vtkTextureImageCache::CacheData& cache = this->Storage->TextTextureCache.GetCacheData(UTF8TextPropertyKey(this->TextProp, string, dpi)); vtkImageData* image = cache.ImageData; if (image->GetNumberOfPoints() == 0 && image->GetNumberOfCells() == 0) { int textDims[2]; if (!tren->RenderString(this->TextProp, string, image, textDims, dpi)) { vtkErrorMacro("Error rendering string: " << string); return; } if (!tren->GetMetrics(this->TextProp, string, cache.Metrics, dpi)) { vtkErrorMacro("Error computing bounding box for string: " << string); return; } } vtkTexture* texture = cache.Texture; texture->Render(this->Renderer); int imgDims[3]; image->GetDimensions(imgDims); float textWidth = static_cast(cache.Metrics.BoundingBox[1] - cache.Metrics.BoundingBox[0] + 1); float textHeight = static_cast(cache.Metrics.BoundingBox[3] - cache.Metrics.BoundingBox[2] + 1); float width = textWidth / xScale; float height = textHeight / yScale; float xw = textWidth / static_cast(imgDims[0]); float xh = textHeight / static_cast(imgDims[1]); // Align the text (the 0 point of the bounding box is aligned to the // rotated and justified anchor point, so just translate by the bbox origin): p[0] += cache.Metrics.BoundingBox[0] / xScale; p[1] += cache.Metrics.BoundingBox[2] / yScale; float points[] = { p[0], p[1], p[0] + width, p[1], p[0] + width, p[1] + height, p[0], p[1], p[0] + width, p[1] + height, p[0], p[1] + height }; float texCoord[] = { 0.0f, 0.0f, xw, 0.0f, xw, xh, 0.0f, 0.0f, xw, xh, 0.0f, xh }; vtkOpenGLClearErrorMacro(); this->ReadyVTBOProgram(); vtkOpenGLHelper* cbo = this->VTBO; if (!cbo->Program) { return; } int tunit = vtkOpenGLTexture::SafeDownCast(texture)->GetTextureUnit(); cbo->Program->SetUniformi("texture1", tunit); this->BuildVBO(cbo, points, 6, nullptr, 0, texCoord); this->SetMatrices(cbo->Program); glDrawArrays(GL_TRIANGLES, 0, 6); texture->PostRender(this->Renderer); vtkOpenGLCheckErrorMacro("failed after DrawString"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawMathTextString(float point[2], const vtkStdString& string) { // The default text renderer detects and handles mathtext now. Just use the // regular implementation. this->DrawString(point, string); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawImage(float p[2], float scale, vtkImageData* image) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps) { switch (gl2ps->GetActiveState()) { case vtkOpenGLGL2PSHelper::Capture: this->DrawImageGL2PS(p, scale, image); return; case vtkOpenGLGL2PSHelper::Background: return; // Do nothing. case vtkOpenGLGL2PSHelper::Inactive: break; // Draw as normal. } } vtkOpenGLClearErrorMacro(); this->SetTexture(image); this->Storage->Texture->Render(this->Renderer); int* extent = image->GetExtent(); float points[] = { p[0], p[1], p[0] + scale * extent[1] + 1.0f, p[1], p[0] + scale * extent[1] + 1.0f, p[1] + scale * extent[3] + 1.0f, p[0], p[1], p[0] + scale * extent[1] + 1.0f, p[1] + scale * extent[3] + 1.0f, p[0], p[1] + scale * extent[3] + 1.0f }; float texCoord[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; vtkOpenGLClearErrorMacro(); this->ReadyVTBOProgram(); vtkOpenGLHelper* cbo = this->VTBO; if (!cbo->Program) { return; } int tunit = vtkOpenGLTexture::SafeDownCast(this->Storage->Texture)->GetTextureUnit(); cbo->Program->SetUniformi("texture1", tunit); this->BuildVBO(cbo, points, 6, nullptr, 0, texCoord); this->SetMatrices(cbo->Program); glDrawArrays(GL_TRIANGLES, 0, 6); this->Storage->Texture->PostRender(this->Renderer); vtkOpenGLCheckErrorMacro("failed after DrawImage"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawPolyData( float p[2], float scale, vtkPolyData* polyData, vtkUnsignedCharArray* colors, int scalarMode) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps) { switch (gl2ps->GetActiveState()) { case vtkOpenGLGL2PSHelper::Capture: // TODO Implement PolyDataGL2PS // this->DrawPolyDataGL2PS(pos, image); return; case vtkOpenGLGL2PSHelper::Background: return; // Do nothing. case vtkOpenGLGL2PSHelper::Inactive: break; // Draw as normal. } } if (SkipDraw()) { return; } if (polyData->GetLines()->GetNumberOfCells() > 0) { this->PolyDataImpl->Draw(CellArrayHelper::LINE, polyData, polyData->GetPoints(), p[0], p[1], scale, scalarMode, colors); } if (polyData->GetPolys()->GetNumberOfCells() > 0) { this->PolyDataImpl->Draw(CellArrayHelper::POLYGON, polyData, polyData->GetPoints(), p[0], p[1], scale, scalarMode, colors); } } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawImage(const vtkRectf& pos, vtkImageData* image) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps) { switch (gl2ps->GetActiveState()) { case vtkOpenGLGL2PSHelper::Capture: this->DrawImageGL2PS(pos, image); return; case vtkOpenGLGL2PSHelper::Background: return; // Do nothing. case vtkOpenGLGL2PSHelper::Inactive: break; // Draw as normal. } } int tunit = this->RenderWindow->GetTextureUnitManager()->Allocate(); if (tunit < 0) { vtkErrorMacro("Hardware does not support the number of textures defined."); return; } this->RenderWindow->GetState()->vtkglActiveTexture(GL_TEXTURE0 + tunit); vtkVector2f tex(1.0, 1.0); // Call this *after* calling vtkglActiveTexture() to ensure the texture // is bound to the correct texture unit. GLuint index = this->Storage->TextureFromImage(image, tex); float points[] = { pos.GetX(), pos.GetY(), pos.GetX() + pos.GetWidth(), pos.GetY(), pos.GetX() + pos.GetWidth(), pos.GetY() + pos.GetHeight(), pos.GetX(), pos.GetY(), pos.GetX() + pos.GetWidth(), pos.GetY() + pos.GetHeight(), pos.GetX(), pos.GetY() + pos.GetHeight() }; float texCoord[] = { 0.0f, 0.0f, tex[0], 0.0f, tex[0], tex[1], 0.0f, 0.0f, tex[0], tex[1], 0.0f, tex[1] }; this->ReadyVTBOProgram(); vtkOpenGLHelper* cbo = this->VTBO; if (!cbo->Program) { return; } cbo->Program->SetUniformi("texture1", tunit); this->BuildVBO(cbo, points, 6, nullptr, 0, texCoord); this->SetMatrices(cbo->Program); glDrawArrays(GL_TRIANGLES, 0, 6); this->RenderWindow->GetTextureUnitManager()->Free(tunit); glDeleteTextures(1, &index); vtkOpenGLCheckErrorMacro("failed after DrawImage"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetColor4(unsigned char*) { vtkErrorMacro("color cannot be set this way\n"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetColor(unsigned char*) { vtkErrorMacro("color cannot be set this way\n"); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetTexture(vtkImageData* image, int properties) { if (image == nullptr) { if (this->Storage->Texture) { this->Storage->Texture->Delete(); this->Storage->Texture = nullptr; } return; } if (this->Storage->Texture == nullptr) { this->Storage->Texture = vtkTexture::New(); } this->Storage->Texture->SetInputData(image); this->Storage->TextureProperties = properties; this->Storage->Texture->SetRepeat(properties & vtkContextDevice2D::Repeat); this->Storage->Texture->SetInterpolate(properties & vtkContextDevice2D::Linear); this->Storage->Texture->EdgeClampOn(); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetPointSize(float size) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { gl2ps->SetPointSize(size); } this->RenderWindow->GetState()->vtkglPointSize(size); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetLineWidth(float width) { vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { gl2ps->SetLineWidth(width); } this->RenderWindow->GetState()->vtkglLineWidth(width); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetLineType(int type) { this->LinePattern = 0x0000; switch (type) { case vtkPen::NO_PEN: this->LinePattern = 0x0000; break; case vtkPen::DASH_LINE: this->LinePattern = 0x00FF; break; case vtkPen::DOT_LINE: this->LinePattern = 0x0101; break; case vtkPen::DASH_DOT_LINE: this->LinePattern = 0x0C0F; break; case vtkPen::DASH_DOT_DOT_LINE: this->LinePattern = 0x1C47; break; case vtkPen::DENSE_DOT_LINE: this->LinePattern = 0x1111; break; default: this->LinePattern = 0xFFFF; } vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); if (gl2ps && gl2ps->GetActiveState() == vtkOpenGLGL2PSHelper::Capture) { gl2ps->SetLineStipple(this->LinePattern); } } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::MultiplyMatrix(vtkMatrix3x3* m) { // We must construct a 4x4 matrix from the 3x3 matrix for OpenGL double* M = m->GetData(); double matrix[16]; matrix[0] = M[0]; matrix[1] = M[1]; matrix[2] = 0.0; matrix[3] = M[2]; matrix[4] = M[3]; matrix[5] = M[4]; matrix[6] = 0.0; matrix[7] = M[5]; matrix[8] = 0.0; matrix[9] = 0.0; matrix[10] = 1.0; matrix[11] = 0.0; matrix[12] = M[6]; matrix[13] = M[7]; matrix[14] = 0.0; matrix[15] = M[8]; this->ModelMatrix->Concatenate(matrix); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetMatrix(vtkMatrix3x3* m) { // We must construct a 4x4 matrix from the 3x3 matrix for OpenGL double* M = m->GetData(); double matrix[16]; matrix[0] = M[0]; matrix[1] = M[1]; matrix[2] = 0.0; matrix[3] = M[2]; matrix[4] = M[3]; matrix[5] = M[4]; matrix[6] = 0.0; matrix[7] = M[5]; matrix[8] = 0.0; matrix[9] = 0.0; matrix[10] = 1.0; matrix[11] = 0.0; matrix[12] = M[6]; matrix[13] = M[7]; matrix[14] = 0.0; matrix[15] = M[8]; this->ModelMatrix->SetMatrix(matrix); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::GetMatrix(vtkMatrix3x3* m) { assert("pre: non_null" && m != nullptr); // We must construct a 4x4 matrix from the 3x3 matrix for OpenGL double* M = m->GetData(); double* matrix = this->ModelMatrix->GetMatrix()->Element[0]; M[0] = matrix[0]; M[1] = matrix[1]; M[2] = matrix[3]; M[3] = matrix[4]; M[4] = matrix[5]; M[5] = matrix[7]; M[6] = matrix[12]; M[7] = matrix[13]; M[8] = matrix[15]; m->Modified(); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::PushMatrix() { this->ModelMatrix->Push(); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::PopMatrix() { this->ModelMatrix->Pop(); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::SetClipping(int* dim) { // If the window is using tile scaling, we need to update the clip coordinates // relative to the tile being rendered. // (see paraview/paraview#17308) double tileViewPort[4]; this->Renderer->GetVTKWindow()->GetTileViewport(tileViewPort); this->Renderer->NormalizedDisplayToDisplay(tileViewPort[0], tileViewPort[1]); this->Renderer->NormalizedDisplayToDisplay(tileViewPort[2], tileViewPort[3]); vtkRecti tileRect{ vtkContext2D::FloatToInt(tileViewPort[0]), vtkContext2D::FloatToInt(tileViewPort[1]), 0, 0 }; tileRect.AddPoint( vtkContext2D::FloatToInt(tileViewPort[2]), vtkContext2D::FloatToInt(tileViewPort[3])); // tileRect is the tile being rendered in the current RenderWindow in pixels. double viewport[4]; this->Renderer->GetViewport(viewport); this->Renderer->NormalizedDisplayToDisplay(viewport[0], viewport[1]); this->Renderer->NormalizedDisplayToDisplay(viewport[2], viewport[3]); vtkRecti rendererRect{ vtkContext2D::FloatToInt(viewport[0]), vtkContext2D::FloatToInt(viewport[1]), 0, 0 }; rendererRect.AddPoint( vtkContext2D::FloatToInt(viewport[2]), vtkContext2D::FloatToInt(viewport[3])); // rendererRect is the viewport in pixels. // `dim` is specified as (x,y,width,height) relative to the viewport that this // prop is rendering in. So let's fit it in the viewport rect i.e. // rendererRect vtkRecti clipRect{ dim[0], dim[1], dim[2], dim[3] }; clipRect.MoveTo(clipRect.GetX() + rendererRect.GetX(), clipRect.GetY() + rendererRect.GetY()); clipRect.Intersect(rendererRect); // Now, clamp the clipRect to the region being shown on the current tile. This // generally has no effect since clipRect is wholly contained in tileRect // unless tile scaling was being used. In either case, this method will return // true as long as the rectangle intersection will produce a valid rectangle. if (clipRect.Intersect(tileRect)) { // offset clipRect relative to current tile i.e. window. clipRect.MoveTo(clipRect.GetX() - tileRect.GetX(), clipRect.GetY() - tileRect.GetY()); } else { // clipping region results in empty region, just set to empty. clipRect = vtkRecti{ 0, 0, 0, 0 }; } assert(clipRect.GetWidth() >= 0 && clipRect.GetHeight() >= 0); this->RenderWindow->GetState()->vtkglScissor( clipRect.GetX(), clipRect.GetY(), clipRect.GetWidth(), clipRect.GetHeight()); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::EnableClipping(bool enable) { this->RenderWindow->GetState()->SetEnumState(GL_SCISSOR_TEST, enable); } //------------------------------------------------------------------------------ bool vtkOpenGLContextDevice2D::SetStringRendererToFreeType() { // FreeType is the only choice - nothing to do here return true; } //------------------------------------------------------------------------------ bool vtkOpenGLContextDevice2D::SetStringRendererToQt() { // The Qt based strategy is not available return false; } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::ReleaseGraphicsResources(vtkWindow* window) { this->VBO->ReleaseGraphicsResources(window); this->VCBO->ReleaseGraphicsResources(window); this->LinesBO->ReleaseGraphicsResources(window); this->LinesCBO->ReleaseGraphicsResources(window); this->SBO->ReleaseGraphicsResources(window); this->SCBO->ReleaseGraphicsResources(window); this->VTBO->ReleaseGraphicsResources(window); if (this->Storage->Texture) { this->Storage->Texture->ReleaseGraphicsResources(window); } if (this->Storage->SpriteTexture) { this->Storage->SpriteTexture->ReleaseGraphicsResources(window); } this->Storage->TextTextureCache.ReleaseGraphicsResources(window); } //------------------------------------------------------------------------------ bool vtkOpenGLContextDevice2D::HasGLSL() { return true; } //------------------------------------------------------------------------------ vtkImageData* vtkOpenGLContextDevice2D::GetMarker(int shape, int size, bool highlight) { // Generate the cache key for this marker vtkTypeUInt64 key = highlight ? (1U << 31) : 0U; key |= static_cast(shape); key <<= 32; key |= static_cast(size); // Try to find the marker in the cache std::list::iterator match = std::find(this->MarkerCache.begin(), this->MarkerCache.end(), key); // Was it in the cache? if (match != this->MarkerCache.end()) { // Yep -- move it to the front and return the data. if (match == this->MarkerCache.begin()) { return match->Value; } else { vtkMarkerCacheObject result = *match; this->MarkerCache.erase(match); this->MarkerCache.push_front(result); return result.Value; } } // Nope -- we'll need to generate it. Create the image data: vtkMarkerCacheObject result; result.Key = key; result.Value = this->GenerateMarker(shape, size, highlight); // If there was an issue generating the marker, just return nullptr. if (!result.Value) { vtkErrorMacro(<< "Error generating marker: shape,size: " << shape << "," << size); return nullptr; } // Check the current cache size. while (this->MarkerCache.size() > static_cast(this->MaximumMarkerCacheSize - 1) && !this->MarkerCache.empty()) { this->MarkerCache.back().Value->Delete(); this->MarkerCache.pop_back(); } // Add to the cache this->MarkerCache.push_front(result); return result.Value; } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::ComputeStringBoundsInternal( const std::string& string, float bounds[4]) { vtkTextRenderer* tren = vtkTextRenderer::GetInstance(); if (!tren) { vtkErrorMacro("No text renderer available. Link to vtkRenderingFreeType " "to get the default implementation."); return; } // TODO this currently ignores vtkContextScene::ScaleTiles. Not sure how to // get at that from here, but this is better than ignoring scaling altogether. // TODO Also, FreeType supports anisotropic DPI. Might be needed if the // tileScale isn't homogeneous, but we'll need to update the textrenderer API // and see if MPL/mathtext can support it. int tileScale[2]; this->RenderWindow->GetTileScale(tileScale); int dpi = this->RenderWindow->GetDPI() * std::max(tileScale[0], tileScale[1]); int bbox[4]; if (!tren->GetBoundingBox(this->TextProp, string, bbox, dpi)) { vtkErrorMacro("Error computing bounding box for string: " << string); return; } // Check for invalid bounding box if (bbox[0] >= bbox[1] || bbox[2] >= bbox[3]) { bounds[0] = 0.f; bounds[1] = 0.f; bounds[2] = 0.f; bounds[3] = 0.f; return; } double* mv = this->ModelMatrix->GetMatrix()->Element[0]; float xScale = mv[0]; float yScale = mv[5]; bounds[0] = static_cast(bbox[0]) / xScale; bounds[1] = static_cast(bbox[2]) / yScale; bounds[2] = static_cast((bbox[1] - bbox[0] + 1) / xScale); bounds[3] = static_cast((bbox[3] - bbox[2] + 1) / yScale); } //------------------------------------------------------------------------------ vtkImageData* vtkOpenGLContextDevice2D::GenerateMarker(int shape, int width, bool highlight) { // Set up the image data, if highlight then the mark shape is different vtkImageData* result = vtkImageData::New(); result->SetExtent(0, width - 1, 0, width - 1, 0, 0); result->AllocateScalars(VTK_UNSIGNED_CHAR, 4); unsigned char* image = static_cast(result->GetScalarPointer()); memset(image, 0, width * width * 4); // Generate the marker image at the required size switch (shape) { case VTK_MARKER_CROSS: { int center = (width + 1) / 2; for (int i = 0; i < center; ++i) { int j = width - i - 1; memset(image + (4 * (width * i + i)), 255, 4); memset(image + (4 * (width * i + j)), 255, 4); memset(image + (4 * (width * j + i)), 255, 4); memset(image + (4 * (width * j + j)), 255, 4); if (highlight) { memset(image + (4 * (width * (j - 1) + (i))), 255, 4); memset(image + (4 * (width * (i + 1) + (i))), 255, 4); memset(image + (4 * (width * (i) + (i + 1))), 255, 4); memset(image + (4 * (width * (i) + (j - 1))), 255, 4); memset(image + (4 * (width * (i + 1) + (j))), 255, 4); memset(image + (4 * (width * (j - 1) + (j))), 255, 4); memset(image + (4 * (width * (j) + (j - 1))), 255, 4); memset(image + (4 * (width * (j) + (i + 1))), 255, 4); } } break; } default: // Maintaining old behavior, which produces plus for unknown shape vtkWarningMacro(<< "Invalid marker shape: " << shape); VTK_FALLTHROUGH; case VTK_MARKER_PLUS: { int center = (width + 1) / 2; for (int i = 0; i < center; ++i) { int j = width - i - 1; int c = center - 1; memset(image + (4 * (width * c + i)), 255, 4); memset(image + (4 * (width * c + j)), 255, 4); memset(image + (4 * (width * i + c)), 255, 4); memset(image + (4 * (width * j + c)), 255, 4); if (highlight) { memset(image + (4 * (width * (c - 1) + i)), 255, 4); memset(image + (4 * (width * (c + 1) + i)), 255, 4); memset(image + (4 * (width * (c - 1) + j)), 255, 4); memset(image + (4 * (width * (c + 1) + j)), 255, 4); memset(image + (4 * (width * i + (c - 1))), 255, 4); memset(image + (4 * (width * i + (c + 1))), 255, 4); memset(image + (4 * (width * j + (c - 1))), 255, 4); memset(image + (4 * (width * j + (c + 1))), 255, 4); } } break; } case VTK_MARKER_SQUARE: { memset(image, 255, width * width * 4); break; } case VTK_MARKER_CIRCLE: { double r = width / 2.0; double r2 = r * r; for (int i = 0; i < width; ++i) { double dx2 = (i - r) * (i - r); for (int j = 0; j < width; ++j) { double dy2 = (j - r) * (j - r); if ((dx2 + dy2) < r2) { memset(image + (4 * width * i) + (4 * j), 255, 4); } } } break; } case VTK_MARKER_DIAMOND: { int r = width / 2; for (int i = 0; i < width; ++i) { int dx = abs(i - r); for (int j = 0; j < width; ++j) { int dy = abs(j - r); if (r - dx >= dy) { memset(image + (4 * width * i) + (4 * j), 255, 4); } } } break; } } return result; } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Renderer: "; if (this->Renderer) { os << endl; this->Renderer->PrintSelf(os, indent.GetNextIndent()); } else { os << "(none)" << endl; } os << indent << "MaximumMarkerCacheSize: " << this->MaximumMarkerCacheSize << endl; os << indent << "MarkerCache: " << this->MarkerCache.size() << " entries." << endl; } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawMarkersGL2PS( int shape, bool highlight, float* points, int n, unsigned char* colors, int nc_comps) { switch (shape) { case VTK_MARKER_CROSS: this->DrawCrossMarkersGL2PS(highlight, points, n, colors, nc_comps); break; default: // default is here for consistency with old impl -- defaults to plus for // unrecognized shapes. case VTK_MARKER_PLUS: this->DrawPlusMarkersGL2PS(highlight, points, n, colors, nc_comps); break; case VTK_MARKER_SQUARE: this->DrawSquareMarkersGL2PS(highlight, points, n, colors, nc_comps); break; case VTK_MARKER_CIRCLE: this->DrawCircleMarkersGL2PS(highlight, points, n, colors, nc_comps); break; case VTK_MARKER_DIAMOND: this->DrawDiamondMarkersGL2PS(highlight, points, n, colors, nc_comps); break; } } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawCrossMarkersGL2PS( bool highlight, float* points, int n, unsigned char* colors, int nc_comps) { float oldWidth = this->Pen->GetWidth(); unsigned char oldColor[4]; this->Pen->GetColor(oldColor); int oldLineType = this->Pen->GetLineType(); float halfWidth = oldWidth * 0.5f; float deltaX = halfWidth; float deltaY = halfWidth; this->TransformSize(deltaX, deltaY); if (highlight) { this->Pen->SetWidth(1.5); } else { this->Pen->SetWidth(0.5); } this->Pen->SetLineType(vtkPen::SOLID_LINE); float curLine[4]; unsigned char color[4]; for (int i = 0; i < n; ++i) { float* point = points + (i * 2); if (colors) { color[3] = 255; switch (nc_comps) { case 4: case 3: memcpy(color, colors + (i * nc_comps), nc_comps); break; case 2: color[3] = colors[i * nc_comps + 1]; VTK_FALLTHROUGH; case 1: memset(color, colors[i * nc_comps], 3); break; default: vtkErrorMacro(<< "Invalid number of color components: " << nc_comps); break; } this->Pen->SetColor(color); } // The first line of the cross: curLine[0] = point[0] + deltaX; curLine[1] = point[1] + deltaY; curLine[2] = point[0] - deltaX; curLine[3] = point[1] - deltaY; this->DrawPoly(curLine, 2); // And the second: curLine[0] = point[0] + deltaX; curLine[1] = point[1] - deltaY; curLine[2] = point[0] - deltaX; curLine[3] = point[1] + deltaY; this->DrawPoly(curLine, 2); } this->Pen->SetWidth(oldWidth); this->Pen->SetColor(oldColor); this->Pen->SetLineType(oldLineType); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawPlusMarkersGL2PS( bool highlight, float* points, int n, unsigned char* colors, int nc_comps) { float oldWidth = this->Pen->GetWidth(); unsigned char oldColor[4]; this->Pen->GetColor(oldColor); int oldLineType = this->Pen->GetLineType(); float halfWidth = oldWidth * 0.5f; float deltaX = halfWidth; float deltaY = halfWidth; this->TransformSize(deltaX, deltaY); if (highlight) { this->Pen->SetWidth(1.5); } else { this->Pen->SetWidth(0.5); } this->Pen->SetLineType(vtkPen::SOLID_LINE); float curLine[4]; unsigned char color[4]; for (int i = 0; i < n; ++i) { float* point = points + (i * 2); if (colors) { color[3] = 255; switch (nc_comps) { case 4: case 3: memcpy(color, colors + (i * nc_comps), nc_comps); break; case 2: color[3] = colors[i * nc_comps + 1]; VTK_FALLTHROUGH; case 1: memset(color, colors[i * nc_comps], 3); break; default: vtkErrorMacro(<< "Invalid number of color components: " << nc_comps); break; } this->Pen->SetColor(color); } // The first line of the plus: curLine[0] = point[0] - deltaX; curLine[1] = point[1]; curLine[2] = point[0] + deltaX; curLine[3] = point[1]; this->DrawPoly(curLine, 2); // And the second: curLine[0] = point[0]; curLine[1] = point[1] - deltaY; curLine[2] = point[0]; curLine[3] = point[1] + deltaY; this->DrawPoly(curLine, 2); } this->Pen->SetWidth(oldWidth); this->Pen->SetColor(oldColor); this->Pen->SetLineType(oldLineType); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawSquareMarkersGL2PS( bool /*highlight*/, float* points, int n, unsigned char* colors, int nc_comps) { unsigned char oldColor[4]; this->Brush->GetColor(oldColor); this->Brush->SetColor(this->Pen->GetColor()); float halfWidth = this->GetPen()->GetWidth() * 0.5f; float deltaX = halfWidth; float deltaY = halfWidth; this->TransformSize(deltaX, deltaY); float quad[8]; unsigned char color[4]; for (int i = 0; i < n; ++i) { float* point = points + (i * 2); if (colors) { color[3] = 255; switch (nc_comps) { case 4: case 3: memcpy(color, colors + (i * nc_comps), nc_comps); break; case 2: color[3] = colors[i * nc_comps + 1]; VTK_FALLTHROUGH; case 1: memset(color, colors[i * nc_comps], 3); break; default: vtkErrorMacro(<< "Invalid number of color components: " << nc_comps); break; } this->Brush->SetColor(color); } quad[0] = point[0] - deltaX; quad[1] = point[1] - deltaY; quad[2] = point[0] + deltaX; quad[3] = quad[1]; quad[4] = quad[2]; quad[5] = point[1] + deltaY; quad[6] = quad[0]; quad[7] = quad[5]; this->DrawQuad(quad, 4); } this->Brush->SetColor(oldColor); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawCircleMarkersGL2PS( bool /*highlight*/, float* points, int n, unsigned char* colors, int nc_comps) { float radius = this->GetPen()->GetWidth() * 0.475; unsigned char oldColor[4]; this->Brush->GetColor(oldColor); this->Brush->SetColor(this->Pen->GetColor()); unsigned char color[4]; for (int i = 0; i < n; ++i) { float* point = points + (i * 2); if (colors) { color[3] = 255; switch (nc_comps) { case 4: case 3: memcpy(color, colors + (i * nc_comps), nc_comps); break; case 2: color[3] = colors[i * nc_comps + 1]; VTK_FALLTHROUGH; case 1: memset(color, colors[i * nc_comps], 3); break; default: vtkErrorMacro(<< "Invalid number of color components: " << nc_comps); break; } this->Brush->SetColor(color); } this->DrawEllipseWedge(point[0], point[1], radius, radius, 0, 0, 0, 360); } this->Brush->SetColor(oldColor); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawDiamondMarkersGL2PS( bool /*highlight*/, float* points, int n, unsigned char* colors, int nc_comps) { unsigned char oldColor[4]; this->Brush->GetColor(oldColor); this->Brush->SetColor(this->Pen->GetColor()); float halfWidth = this->GetPen()->GetWidth() * 0.5f; float deltaX = halfWidth; float deltaY = halfWidth; this->TransformSize(deltaX, deltaY); float quad[8]; unsigned char color[4]; for (int i = 0; i < n; ++i) { float* point = points + (i * 2); if (colors) { color[3] = 255; switch (nc_comps) { case 4: case 3: memcpy(color, colors + (i * nc_comps), nc_comps); break; case 2: color[3] = colors[i * nc_comps + 1]; VTK_FALLTHROUGH; case 1: memset(color, colors[i * nc_comps], 3); break; default: vtkErrorMacro(<< "Invalid number of color components: " << nc_comps); break; } this->Brush->SetColor(color); } quad[0] = point[0] - deltaX; quad[1] = point[1]; quad[2] = point[0]; quad[3] = point[1] - deltaY; quad[4] = point[0] + deltaX; quad[5] = point[1]; quad[6] = point[0]; quad[7] = point[1] + deltaY; this->DrawQuad(quad, 4); } this->Brush->SetColor(oldColor); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawImageGL2PS(float p[2], vtkImageData* input) { // Must be unsigned char -- otherwise OpenGL rendering behaves badly anyway. if (!vtkDataTypesCompare(input->GetScalarType(), VTK_UNSIGNED_CHAR)) { vtkErrorMacro("Invalid image format: Expected unsigned char scalars."); return; } // Convert to float for GL2PS vtkNew image; image->ShallowCopy(input); vtkDataArray* s = image->GetPointData()->GetScalars(); size_t numVals = (s->GetNumberOfComponents() * s->GetNumberOfTuples()); unsigned char* vals = static_cast(s->GetVoidPointer(0)); vtkNew scalars; scalars->SetNumberOfComponents(s->GetNumberOfComponents()); scalars->SetNumberOfTuples(s->GetNumberOfTuples()); for (size_t i = 0; i < numVals; ++i) { scalars->SetValue(static_cast(i), vals[i] / 255.f); } image->GetPointData()->SetScalars(scalars); // Instance always exists when this method is called: vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); float tp[2] = { p[0], p[1] }; this->TransformPoint(tp[0], tp[1]); double pos[3] = { static_cast(tp[0]), static_cast(tp[1]), 0. }; gl2ps->DrawImage(image, pos); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawImageGL2PS(float p[2], float scale, vtkImageData* image) { if (std::fabs(scale - 1.f) < 1e-5f) { this->DrawImageGL2PS(p, image); return; } int dims[3]; image->GetDimensions(dims); vtkRectf rect(p[0], p[1], dims[0] * scale, dims[1] * scale); this->DrawImageGL2PS(rect, image); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawImageGL2PS(const vtkRectf& rect, vtkImageData* image) { int dims[3]; image->GetDimensions(dims); int width = static_cast(std::round(rect.GetWidth())); int height = static_cast(std::round(rect.GetHeight())); if (width == dims[0] && height == dims[1]) { this->DrawImageGL2PS(rect.GetBottomLeft().GetData(), image); return; } vtkNew resize; resize->SetInputData(image); resize->SetResizeMethod(vtkImageResize::OUTPUT_DIMENSIONS); resize->SetOutputDimensions(width, height, -1); resize->Update(); this->DrawImageGL2PS(rect.GetBottomLeft().GetData(), resize->GetOutput()); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawCircleGL2PS(float x, float y, float rX, float rY) { if (this->Brush->GetColorObject().GetAlpha() == 0) { return; } // We know this is valid if this method has been called: vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); vtkNew path; this->AddEllipseToPath(path, 0.f, 0.f, rX, rY, false); this->TransformPath(path); double origin[3] = { x, y, 0.f }; // Fill unsigned char fillColor[4]; this->Brush->GetColor(fillColor); std::stringstream label; label << "vtkOpenGLContextDevice2D::DrawCircleGL2PS(" << x << ", " << y << ", " << rX << ", " << rY << ") fill:"; gl2ps->DrawPath(path, origin, origin, fillColor, nullptr, 0.0, -1.f, label.str().c_str()); // and stroke unsigned char strokeColor[4]; this->Pen->GetColor(strokeColor); float strokeWidth = this->Pen->GetWidth(); label.str(""); label.clear(); label << "vtkOpenGLContextDevice2D::DrawCircleGL2PS(" << x << ", " << y << ", " << rX << ", " << rY << ") stroke:"; gl2ps->DrawPath( path, origin, origin, strokeColor, nullptr, 0.0, strokeWidth, label.str().c_str()); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::DrawWedgeGL2PS( float x, float y, float outRx, float outRy, float inRx, float inRy) { if (this->Brush->GetColorObject().GetAlpha() == 0) { return; } vtkNew path; this->AddEllipseToPath(path, 0.f, 0.f, outRx, outRy, false); this->AddEllipseToPath(path, 0.f, 0.f, inRx, inRy, true); std::stringstream label; label << "vtkOpenGLGL2PSContextDevice2D::DrawWedgeGL2PS(" << x << ", " << y << ", " << outRx << ", " << outRy << ", " << inRx << ", " << inRy << ") path:"; unsigned char color[4]; this->Brush->GetColor(color); double rasterPos[3] = { static_cast(x), static_cast(y), 0. }; this->TransformPoint(x, y); double windowPos[3] = { static_cast(x), static_cast(y), 0. }; // We know the helper exists and that we are capturing if this function has // been called. vtkOpenGLGL2PSHelper* gl2ps = vtkOpenGLGL2PSHelper::GetInstance(); gl2ps->DrawPath(path, rasterPos, windowPos, color, nullptr, 0.0, -1.f, label.str().c_str()); } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::AddEllipseToPath( vtkPath* path, float x, float y, float rx, float ry, bool reverse) { if (rx < 1e-5 || ry < 1e-5) { return; } // method based on http://www.tinaja.com/glib/ellipse4.pdf static const float MAGIC = (4.f / 3.f) * (sqrt(2.f) - 1.f); if (!reverse) { path->InsertNextPoint(x - rx, y, 0, vtkPath::MOVE_TO); path->InsertNextPoint(x - rx, ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(-rx * MAGIC, y + ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x, y + ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(rx * MAGIC, y + ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x + rx, ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x + rx, y, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x + rx, -ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(rx * MAGIC, y - ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x, y - ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(-rx * MAGIC, y - ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x - rx, -ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x - rx, y, 0, vtkPath::CUBIC_CURVE); } else { path->InsertNextPoint(x - rx, y, 0, vtkPath::MOVE_TO); path->InsertNextPoint(x - rx, -ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(-rx * MAGIC, y - ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x, y - ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(rx * MAGIC, y - ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x + rx, -ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x + rx, y, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x + rx, ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(rx * MAGIC, y + ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x, y + ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(-rx * MAGIC, y + ry, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x - rx, ry * MAGIC, 0, vtkPath::CUBIC_CURVE); path->InsertNextPoint(x - rx, y, 0, vtkPath::CUBIC_CURVE); } } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::TransformPath(vtkPath* path) const { // Transform the path with the modelview matrix: double modelview[16]; vtkMatrix4x4::DeepCopy(modelview, this->ModelMatrix->GetMatrix()); // Transform the 2D path. float newPoint[3] = { 0, 0, 0 }; vtkPoints* points = path->GetPoints(); for (vtkIdType i = 0; i < path->GetNumberOfPoints(); ++i) { double* point = points->GetPoint(i); newPoint[0] = modelview[0] * point[0] + modelview[1] * point[1] + modelview[3]; newPoint[1] = modelview[4] * point[0] + modelview[5] * point[1] + modelview[7]; points->SetPoint(i, newPoint); } } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::TransformPoint(float& x, float& y) const { double modelview[16]; vtkMatrix4x4::DeepCopy(modelview, this->ModelMatrix->GetMatrix()); float inX = x; float inY = y; x = modelview[0] * inX + modelview[1] * inY + modelview[3]; y = modelview[4] * inX + modelview[5] * inY + modelview[7]; } //------------------------------------------------------------------------------ void vtkOpenGLContextDevice2D::TransformSize(float& dx, float& dy) const { double modelview[16]; vtkMatrix4x4::DeepCopy(modelview, this->ModelMatrix->GetMatrix()); dx /= modelview[0]; dy /= modelview[5]; }