/*////////////////////////////////////////////////////////////////////////////// // CMT Cosmic Muon Tomography project ////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova All rights reserved Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it > ------------------------------------------------------------------ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3.0 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. //////////////////////////////////////////////////////////////////////////////*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "uLibVtkViewer.h" // Custom interactor style: disables spin/inertia so the scene only // rotates while the mouse is actively being moved with the button held. class vtkInteractorStyleNoSpin : public vtkInteractorStyleTrackballCamera { public: static vtkInteractorStyleNoSpin *New(); vtkTypeMacro(vtkInteractorStyleNoSpin, vtkInteractorStyleTrackballCamera); // Override: when the left button is released, immediately stop any // ongoing motion (rotation/spin) so no momentum is carried over. void OnLeftButtonUp() override { this->StopState(); vtkInteractorStyleTrackballCamera::OnLeftButtonUp(); } }; vtkStandardNewMacro(vtkInteractorStyleNoSpin); namespace uLib { namespace Vtk { struct ViewerData { vtkRenderWindow *m_RenderWindow; vtkSmartPointer m_Interactor; vtkSmartPointer m_GridButton; vtkSmartPointer m_ProjButton; ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {} ~ViewerData() { if (m_Interactor) { m_Interactor->SetRenderWindow(nullptr); } m_RenderWindow->Delete(); } }; //////////////////////////////////////////////////////////////////////////////// ///// VTK VIEWER ////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// Viewer::Viewer() : Viewport(), dv(new ViewerData()) { InstallPipe(); } Viewer::~Viewer() { this->DisableHandler(); if (dv->m_GridButton) { dv->m_GridButton->Off(); dv->m_GridButton->SetInteractor(nullptr); dv->m_GridButton = nullptr; } if (dv->m_ProjButton) { dv->m_ProjButton->Off(); dv->m_ProjButton->SetInteractor(nullptr); dv->m_ProjButton = nullptr; } if (this->GetRenderWindow()) { this->GetRenderWindow()->RemoveAllObservers(); } if (this->GetInteractor()) { this->GetInteractor()->RemoveAllObservers(); } UninstallPipe(); delete dv; } void Viewer::InstallPipe() { dv->m_RenderWindow->AddRenderer(this->GetRenderer()); dv->m_RenderWindow->SetSize(600,600); if (std::getenv("CTEST_PROJECT_NAME")) { dv->m_RenderWindow->SetOffScreenRendering(1); } dv->m_Interactor = vtkSmartPointer::New(); dv->m_Interactor->SetRenderWindow(dv->m_RenderWindow); // Common setup Viewport::SetupPipeline(dv->m_Interactor); // Setup native grid button if (!std::getenv("CTEST_PROJECT_NAME")) { SetupGridButton(); SetupProjButton(); } // BUT we want to override the style with our custom NoSpin version vtkSmartPointer style = vtkSmartPointer::New(); dv->m_Interactor->SetInteractorStyle(style); // Must be rendered here in Vtk-6.0 or seg-fault // if (!std::getenv("CTEST_PROJECT_NAME")) { dv->m_RenderWindow->Render(); } } void Viewer::UninstallPipe() { } void Viewer::Render() { if (dv->m_RenderWindow) dv->m_RenderWindow->Render(); } vtkCameraOrientationWidget * Viewer::MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor, vtkRenderer *renderer) { vtkSmartPointer widget = vtkSmartPointer::New(); widget->SetParentRenderer(renderer); widget->SetInteractor(interactor); widget->On(); return widget; } void Viewer::SetupGridButton() { if (!dv->m_RenderWindow || !dv->m_RenderWindow->GetInteractor()) return; // Create procedural textures for the button using canvas vtkNew canvas; canvas->SetScalarTypeToUnsignedChar(); canvas->SetNumberOfScalarComponents(4); canvas->SetExtent(0, 63, 0, 63, 0, 0); // State 0: OFF (Gray circle, transparent background) canvas->SetDrawColor(0, 0, 0, 0); canvas->FillBox(0, 63, 0, 63); canvas->SetDrawColor(120, 120, 120, 255); canvas->DrawCircle(32, 32, 25); canvas->Update(); vtkNew imgOff; imgOff->DeepCopy(canvas->GetOutput()); // State 1: ON (White circle, transparent background) canvas->SetDrawColor(0, 0, 0, 0); canvas->FillBox(0, 63, 0, 63); canvas->SetDrawColor(255, 255, 255, 255); canvas->DrawCircle(32, 32, 25); canvas->Update(); vtkNew imgOn; imgOn->DeepCopy(canvas->GetOutput()); vtkNew rep; rep->SetNumberOfStates(2); rep->SetButtonTexture(0, imgOff); rep->SetButtonTexture(1, imgOn); dv->m_GridButton = vtkSmartPointer::New(); dv->m_GridButton->SetInteractor(dv->m_RenderWindow->GetInteractor()); dv->m_GridButton->SetRepresentation(rep); // Position it initially UpdateGridButtonPosition(); // Callback for resize (ModifiedEvent on RenderWindow) vtkNew resizeCallback; resizeCallback->SetClientData(this); resizeCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){ auto* v = static_cast(clientdata); v->UpdateGridButtonPosition(); }); dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback); // Callback for state change vtkNew stateCallback; stateCallback->SetClientData(this); stateCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){ auto* btn = vtkButtonWidget::SafeDownCast(caller); auto* v = static_cast(clientdata); auto* r = vtkTexturedButtonRepresentation2D::SafeDownCast(btn->GetRepresentation()); v->SetGridVisible(r->GetState() == 1); }); dv->m_GridButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback); dv->m_GridButton->On(); // Set initial state rep->SetState(GetGridVisible() ? 1 : 0); } void Viewer::UpdateGridButtonPosition() { if (!dv->m_GridButton || !dv->m_RenderWindow) return; auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(dv->m_GridButton->GetRepresentation()); if (!rep) return; int *sz = dv->m_RenderWindow->GetSize(); if (sz[0] == 0 || sz[1] == 0) return; // Window not yet sized or hidden int margin_rigth = 23; int margin_top = 170; int btnSz = 100; // Button size in display coordinates double bds[6] = { (double)sz[0] - btnSz - margin_rigth, (double)sz[0] - margin_rigth, (double)sz[1] - margin_top - btnSz/2.0, (double)sz[1] - margin_top + btnSz/2.0, 0, 0 }; rep->PlaceWidget(bds); } void Viewer::SetupProjButton() { if (!dv->m_RenderWindow || !dv->m_RenderWindow->GetInteractor()) return; vtkNew canvas; canvas->SetScalarTypeToUnsignedChar(); canvas->SetNumberOfScalarComponents(4); canvas->SetExtent(0, 63, 0, 63, 0, 0); // State 0: Perspective (gray trapezoid-like lines) canvas->SetDrawColor(0, 0, 0, 0); canvas->FillBox(0, 63, 0, 63); canvas->SetDrawColor(120, 120, 120, 255); canvas->DrawSegment(16, 16, 48, 16); canvas->DrawSegment(48, 16, 56, 48); canvas->DrawSegment(56, 48, 8, 48); canvas->DrawSegment(8, 48, 16, 16); canvas->Update(); vtkNew imgPersp; imgPersp->DeepCopy(canvas->GetOutput()); // State 1: Orthographic (white rectangle) canvas->SetDrawColor(0, 0, 0, 0); canvas->FillBox(0, 63, 0, 63); canvas->SetDrawColor(255, 255, 255, 255); canvas->DrawSegment(12, 16, 52, 16); canvas->DrawSegment(52, 16, 52, 48); canvas->DrawSegment(52, 48, 12, 48); canvas->DrawSegment(12, 48, 12, 16); canvas->Update(); vtkNew imgOrtho; imgOrtho->DeepCopy(canvas->GetOutput()); vtkNew rep; rep->SetNumberOfStates(2); rep->SetButtonTexture(0, imgPersp); rep->SetButtonTexture(1, imgOrtho); dv->m_ProjButton = vtkSmartPointer::New(); dv->m_ProjButton->SetInteractor(dv->m_RenderWindow->GetInteractor()); dv->m_ProjButton->SetRepresentation(rep); UpdateProjButtonPosition(); vtkNew resizeCallback; resizeCallback->SetClientData(this); resizeCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){ static_cast(clientdata)->UpdateProjButtonPosition(); }); dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback); vtkNew stateCallback; stateCallback->SetClientData(this); stateCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){ auto* btn = vtkButtonWidget::SafeDownCast(caller); auto* v = static_cast(clientdata); auto* r = vtkTexturedButtonRepresentation2D::SafeDownCast(btn->GetRepresentation()); v->SetParallelProjection(r->GetState() == 1); }); dv->m_ProjButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback); dv->m_ProjButton->On(); rep->SetState(GetParallelProjection() ? 1 : 0); } void Viewer::UpdateProjButtonPosition() { if (!dv->m_ProjButton || !dv->m_RenderWindow) return; auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(dv->m_ProjButton->GetRepresentation()); if (!rep) return; int *sz = dv->m_RenderWindow->GetSize(); if (sz[0] == 0 || sz[1] == 0) return; int margin_right = 23; int margin_top = 220; // below the grid button (170 + 50) int btnSz = 100; double bds[6] = { (double)sz[0] - btnSz - margin_right, (double)sz[0] - margin_right, (double)sz[1] - margin_top - btnSz/2.0, (double)sz[1] - margin_top + btnSz/2.0, 0, 0 }; rep->PlaceWidget(bds); } void Viewer::Start() { if (std::getenv("CTEST_PROJECT_NAME")) return; dv->m_RenderWindow->GetInteractor()->Start(); } vtkRenderWindow *Viewer::GetRenderWindow() { return dv->m_RenderWindow; } vtkRenderWindowInteractor *Viewer::GetInteractor() { return dv->m_RenderWindow->GetInteractor(); } } // namespace Vtk } // namespace uLib