/*////////////////////////////////////////////////////////////////////////////// // 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. //////////////////////////////////////////////////////////////////////////////*/ #if VTK_MAJOR_VERSION <= 5 # #else # include #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "vtkViewport.h" #include "uLibVtkInterface.h" #include "Math/Transform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "uLibVtkInterface.h" #include "vtkHandlerWidget.h" #include "Vtk/Math/vtkDense.h" #include "Vtk/Math/vtkDense.h" #include "Core/Property.h" #include "Math/Transform.h" namespace uLib { namespace Vtk { // PIMPL -------------------------------------------------------------------- // class Prop3DData { public: Prop3DData(Prop3D* owner) : m_Prop3D(owner), m_Renderers(vtkSmartPointer::New()), m_Prop(nullptr), m_ShowBoundingBox(false), m_ShowScaleMeasures(false), m_Representation(Prop3D::Surface), m_Opacity(1.0), m_Selectable(true), m_Selected(false), m_Visibility(true), m_Dragable(true), m_HighlightMode(Prop3D::HighlightPlain) { m_Color = Vector3d(-1, -1, -1); } ~Prop3DData() { // No manual Delete needed for smart pointers } Prop3D *m_Prop3D; // members // vtkSmartPointer m_Renderers; vtkSmartPointer m_Prop; vtkSmartPointer m_OutlineSource; vtkSmartPointer m_OutlineActor; vtkSmartPointer m_CubeAxesActor; vtkSmartPointer m_HighlightActor; // Display properties bool m_ShowBoundingBox; bool m_ShowScaleMeasures; int m_Representation; // 0: Points, 1: Wireframe, 2: Surface, 3: SurfaceWithEdges, 4: Volume, 5: Outline, 6: Slice Vector3d m_Color; double m_Opacity; bool m_Selectable; bool m_Selected; bool m_Visibility; bool m_Dragable; int m_HighlightMode; // 0: Plain, 1: Corners // TRS m_Transform; void ApplyAppearance(vtkProp *p) { if (!p) return; p->SetVisibility(m_Visibility); p->SetPickable(m_Selectable); p->SetDragable(m_Dragable); vtkActor *actor = vtkActor::SafeDownCast(p); if (actor) { if (m_Representation != -1 && m_Representation != Prop3D::Volume) { if (m_Representation == Prop3D::SurfaceWithEdges) { actor->GetProperty()->SetRepresentation(VTK_SURFACE); actor->GetProperty()->SetEdgeVisibility(1); } else if (m_Representation != Prop3D::Outline && m_Representation != Prop3D::Slice) { actor->GetProperty()->SetRepresentation(m_Representation); actor->GetProperty()->SetEdgeVisibility(0); } } if (m_Color.x() != -1.0) { double c[3] = {m_Color.x(), m_Color.y(), m_Color.z()}; actor->GetProperty()->SetColor(c); } if (m_Opacity != -1.0) { actor->GetProperty()->SetOpacity(m_Opacity); } } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(p)) { // Recursively apply to parts of the assembly vtkProp3DCollection *parts = asm_p->GetParts(); if (parts) { parts->InitTraversal(); for (int i = 0; i < parts->GetNumberOfItems(); ++i) { this->ApplyAppearance(parts->GetNextProp3D()); } } } } void ApplyTransform(vtkProp3D* p3d) { if (p3d) { p3d->SetUserMatrix(nullptr); p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); // Convert Model Radians to VTK Degrees p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, m_Transform.rotation.y() / CLHEP::degree, m_Transform.rotation.z() / CLHEP::degree); p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); } } void UpdateHighlight() { if (m_Selected) { vtkPolyData* polydata = nullptr; if (vtkActor *actor = vtkActor::SafeDownCast(m_Prop)) { if (actor->GetMapper()) { polydata = vtkPolyData::SafeDownCast(actor->GetMapper()->GetInput()); } } else if (vtkAssembly *asm_p = vtkAssembly::SafeDownCast(m_Prop)) { vtkPropCollection *parts = asm_p->GetParts(); if (parts) { parts->InitTraversal(); for (int i = 0; i < parts->GetNumberOfItems(); ++i) { vtkActor *a = vtkActor::SafeDownCast(parts->GetNextProp()); if (a && a->GetMapper()) { polydata = vtkPolyData::SafeDownCast(a->GetMapper()->GetInput()); if (polydata) break; } } } } if (!polydata) { if (m_HighlightActor) { m_Renderers->InitTraversal(); for (int i = 0; i < m_Renderers->GetNumberOfItems(); ++i) { m_Renderers->GetNextItem()->RemoveActor(m_HighlightActor); } m_HighlightActor = nullptr; } return; } if (!m_HighlightActor) { m_HighlightActor = vtkSmartPointer::New(); vtkSmartPointer mapper = vtkSmartPointer::New(); m_HighlightActor->SetMapper(mapper); m_HighlightActor->GetProperty()->SetRepresentationToWireframe(); m_HighlightActor->GetProperty()->SetColor(1.0, 0.0, 0.0); // Red m_HighlightActor->GetProperty()->SetLineWidth(2.0); m_HighlightActor->GetProperty()->SetLighting(0); } if (m_HighlightMode == Prop3D::HighlightPlain) { vtkSmartPointer cube = vtkSmartPointer::New(); double bounds[6]; polydata->GetBounds(bounds); double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]}); double pad = maxDim * 0.02; if(pad < 1e-4) pad = 0.05; cube->SetBounds(bounds[0]-pad, bounds[1]+pad, bounds[2]-pad, bounds[3]+pad, bounds[4]-pad, bounds[5]+pad); cube->Update(); m_HighlightActor->GetMapper()->SetInputConnection(cube->GetOutputPort()); } else { // Corners mode logic double bounds[6]; polydata->GetBounds(bounds); double maxDim = std::max({bounds[1]-bounds[0], bounds[3]-bounds[2], bounds[5]-bounds[4]}); double pad = maxDim * 0.02; if(pad < 1e-4) pad = 0.05; double b[6] = {bounds[0]-pad, bounds[1]+pad, bounds[2]-pad, bounds[3]+pad, bounds[4]-pad, bounds[5]+pad}; vtkNew points; vtkNew lines; float len[3] = { (float)(b[1] - b[0]) * 0.15f, (float)(b[3] - b[2]) * 0.15f, (float)(b[5] - b[4]) * 0.15f }; for (int i = 0; i < 8; ++i) { double p[3]; p[0] = b[(i & 1) ? 1 : 0]; p[1] = b[(i & 2) ? 1 : 0]; p[2] = b[(i & 4) ? 1 : 0]; for (int axis = 0; axis < 3; ++axis) { double p2[3] = {p[0], p[1], p[2]}; double delta = (i & (1 << axis)) ? -len[axis] : len[axis]; p2[axis] += delta; vtkIdType id1 = points->InsertNextPoint(p); vtkIdType id2 = points->InsertNextPoint(p2); lines->InsertNextCell(2); lines->InsertCellPoint(id1); lines->InsertCellPoint(id2); } } vtkNew cornerPoly; cornerPoly->SetPoints(points); cornerPoly->SetLines(lines); if (auto* mapper = vtkPolyDataMapper::SafeDownCast(m_HighlightActor->GetMapper())) { mapper->SetInputData(cornerPoly); } } // Update highlight matrix from the model world matrix if (m_Prop3D) { if (auto* content = m_Prop3D->GetContent()) { if (auto* tr = dynamic_cast(content)) { vtkNew vwm; Matrix4fToVtk(tr->GetWorldMatrix(), vwm); m_HighlightActor->SetUserMatrix(vwm); } } } m_Renderers->InitTraversal(); for (int i = 0; i < m_Renderers->GetNumberOfItems(); ++i) { vtkRenderer *ren = m_Renderers->GetNextItem(); ren->AddActor(m_HighlightActor); } } else { if (m_HighlightActor) { m_Renderers->InitTraversal(); for (int i = 0; i < m_Renderers->GetNumberOfItems(); ++i) { m_Renderers->GetNextItem()->RemoveActor(m_HighlightActor); } } } } }; // -------------------------------------------------------------------------- // Prop3D::Prop3D() : Object(), pd(new Prop3DData(this)) { ULIB_ACTIVATE_DISPLAY_PROPERTIES; } Prop3D::~Prop3D() { delete pd; } vtkProp *Prop3D::GetProp() { return pd->m_Prop; } vtkProp3D *Prop3D::GetProxyProp() { // The handler should manipulate the highlight actor if it exists if (pd->m_HighlightActor) { return pd->m_HighlightActor; } return vtkProp3D::SafeDownCast(this->GetProp()); } void Prop3D::SetProp(vtkProp *prop) { if(prop) { prop->SetPickable(pd->m_Selectable); pd->m_Prop = vtkProp3D::SafeDownCast(prop); pd->ApplyAppearance(prop); // For the first actor added, seed the tracked display values from the VTK // actor's current state so the display properties panel shows meaningful // initial values instead of the -1 "not-overriding" sentinels. if (auto* actor = vtkActor::SafeDownCast(prop)) { vtkProperty* vp = actor->GetProperty(); if (pd->m_Representation < 0) pd->m_Representation = vp->GetRepresentation(); if (pd->m_Opacity < 0) pd->m_Opacity = vp->GetOpacity(); if (pd->m_Color.x() < 0) { double c[3]; vp->GetColor(c); pd->m_Color = Vector3d(c[0], c[1], c[2]); } } } } void Prop3D::RemoveProp(vtkProp *prop) { // TODO } void Prop3D::ApplyAppearance(vtkProp* prop) { pd->ApplyAppearance(prop); } void Prop3D::ApplyTransform(vtkProp3D* p3d) { pd->ApplyTransform(p3d); } vtkPropCollection *Prop3D::GetParts() { if (auto* asm_p = vtkAssembly::SafeDownCast(pd->m_Prop)) { return asm_p->GetParts(); } return nullptr; } vtkPropCollection *Prop3D::GetProps() { if (auto* asm_p = vtkAssembly::SafeDownCast(pd->m_Prop)) { return asm_p->GetParts(); } return nullptr; } void Prop3D::ConnectRenderer(vtkRenderer *renderer) { if(renderer) { this->GetRenderers()->AddItem(renderer); if(vtkProp* prop = this->GetProp()) { renderer->AddViewProp(prop); } if (pd->m_ShowBoundingBox && pd->m_OutlineActor) renderer->AddActor(pd->m_OutlineActor); if (pd->m_ShowScaleMeasures && pd->m_CubeAxesActor) { pd->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera()); renderer->AddActor(pd->m_CubeAxesActor); } if (pd->m_Selected && pd->m_HighlightActor) { renderer->AddActor(pd->m_HighlightActor); } } } void Prop3D::DisconnectRenderer(vtkRenderer *renderer) { if(renderer) { if(vtkProp* prop = this->GetProp()) renderer->RemoveViewProp(prop); if (pd->m_ShowBoundingBox && pd->m_OutlineActor) renderer->RemoveActor(pd->m_OutlineActor); if (pd->m_ShowScaleMeasures && pd->m_CubeAxesActor) renderer->RemoveActor(pd->m_CubeAxesActor); this->GetRenderers()->RemoveItem(renderer); } } void Prop3D::AddToViewer(Viewport &viewer) { viewer.AddProp3D(*this); } void Prop3D::RemoveFromViewer(Viewport &viewer) { viewer.RemoveProp3D(*this); } vtkRendererCollection *Prop3D::GetRenderers() const { return pd->m_Renderers; } void Prop3D::PrintSelf(std::ostream &o) const { o << "Props Assembly: \n"; if (pd->m_Prop) pd->m_Prop->PrintSelf(o,vtkIndent(1)); o << "Connected Renderers: \n"; pd->m_Renderers->PrintSelf(o,vtkIndent(1)); } void Prop3D::ShowBoundingBox(bool show) { if (pd->m_ShowBoundingBox == show) return; pd->m_ShowBoundingBox = show; if (show) { if (!pd->m_OutlineActor) { pd->m_OutlineSource = vtkSmartPointer::New(); pd->m_OutlineActor = vtkSmartPointer::New(); vtkSmartPointer mapper = vtkSmartPointer::New(); mapper->SetInputConnection(pd->m_OutlineSource->GetOutputPort()); pd->m_OutlineActor->SetMapper(mapper); pd->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0); } if (pd->m_Prop) { double* bounds = pd->m_Prop->GetBounds(); pd->m_OutlineSource->SetBounds(bounds); pd->m_OutlineSource->Update(); } pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { vtkRenderer *renderer = pd->m_Renderers->GetNextItem(); renderer->AddActor(pd->m_OutlineActor); } } else { if (pd->m_OutlineActor) { pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { vtkRenderer *renderer = pd->m_Renderers->GetNextItem(); renderer->RemoveActor(pd->m_OutlineActor); } } } } void Prop3D::ShowScaleMeasures(bool show) { if (pd->m_ShowScaleMeasures == show) return; pd->m_ShowScaleMeasures = show; if (show) { if (!pd->m_CubeAxesActor) { pd->m_CubeAxesActor = vtkSmartPointer::New(); pd->m_CubeAxesActor->SetFlyModeToOuterEdges(); pd->m_CubeAxesActor->SetUseTextActor3D(1); pd->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0); } if (pd->m_Prop) { double* bounds = pd->m_Prop->GetBounds(); pd->m_CubeAxesActor->SetBounds(bounds); } pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { vtkRenderer *renderer = pd->m_Renderers->GetNextItem(); pd->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera()); renderer->AddActor(pd->m_CubeAxesActor); } } else { if (pd->m_CubeAxesActor) { pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { vtkRenderer *renderer = pd->m_Renderers->GetNextItem(); renderer->RemoveActor(pd->m_CubeAxesActor); } } } } void Prop3D::SetRepresentation(Representation mode) { pd->m_Representation = static_cast(mode); pd->ApplyAppearance(pd->m_Prop); } void Prop3D::SetRepresentation(const char *mode) { std::string s(mode); if (s == "points") SetRepresentation(Points); else if (s == "wireframe") SetRepresentation(Wireframe); else if (s == "shaded" || s == "surface") SetRepresentation(Surface); else if (s == "edges" || s == "surface+edges" || s == "surfacewithedges") SetRepresentation(SurfaceWithEdges); else if (s == "volume") SetRepresentation(Volume); else if (s == "outline") SetRepresentation(Outline); else if (s == "slice") SetRepresentation(Slice); } void Prop3D::SetHighlightMode(HighlightMode mode) { pd->m_HighlightMode = static_cast(mode); pd->UpdateHighlight(); } void Prop3D::SetColor(double r, double g, double b) { pd->m_Color[0] = r; pd->m_Color[1] = g; pd->m_Color[2] = b; pd->ApplyAppearance(pd->m_Prop); } void Prop3D::SetOpacity(double alpha) { pd->m_Opacity = alpha; pd->ApplyAppearance(pd->m_Prop); } void Prop3D::GetColor(double &r, double &g, double &b) const { r = pd->m_Color[0]; g = pd->m_Color[1]; b = pd->m_Color[2]; } double Prop3D::GetOpacity() const { return pd->m_Opacity; } void Prop3D::SetSelectable(bool selectable) { pd->m_Selectable = selectable; pd->ApplyAppearance(pd->m_Prop); } bool Prop3D::IsSelectable() const { return pd->m_Selectable; } void Prop3D::SetSelected(bool selected) { if (!pd->m_Selectable) return; if (pd->m_Selected == selected) return; pd->m_Selected = selected; pd->UpdateHighlight(); } bool Prop3D::IsSelected() const { return pd->m_Selected; } void Prop3D::ApplyProp3DTransform(vtkProp3D* prop) { if (!prop) return; if (auto* content = this->GetContent()) { if (auto* tr = dynamic_cast(content)) { vtkNew m; Matrix4fToVtk(tr->GetMatrix(), m); prop->SetUserMatrix(m); prop->Modified(); } } } void Prop3D::SyncFromVtk() { if (auto* content = this->GetContent()) { if (auto* tr = dynamic_cast(content)) { if (auto* proxy = this->GetProxyProp()) { if (vtkMatrix4x4* mat = proxy->GetUserMatrix()) { tr->FromMatrix(VtkToMatrix4f(mat)); content->Updated(); } } } } } void Prop3D::Update() { // Apply content transform via virtual GetProp() / ApplyProp3DTransform(), // so all derived classes benefit without duplicating the matrix code. this->ApplyProp3DTransform(vtkProp3D::SafeDownCast(this->GetProp())); // Use virtual GetProp() for appearance so overriders (e.g. VoxImage) // that never call SetProp() are handled correctly. pd->ApplyAppearance(this->GetProp()); if (pd->m_Selected) { pd->UpdateHighlight(); } if (auto* prop = this->GetProp()) { if (pd->m_ShowBoundingBox && pd->m_OutlineSource) { double* bounds = prop->GetBounds(); pd->m_OutlineSource->SetBounds(bounds); pd->m_OutlineSource->Update(); } if (pd->m_ShowScaleMeasures && pd->m_CubeAxesActor) { pd->m_CubeAxesActor->SetBounds(prop->GetBounds()); } } // Notify that the object has been updated (important for UI refresh) this->Object::Updated(); // Trigger immediate re-render of all connected viewports pd->m_Renderers->InitTraversal(); for (int i = 0; i < pd->m_Renderers->GetNumberOfItems(); ++i) { if (auto* ren = pd->m_Renderers->GetNextItem()) { if (ren->GetRenderWindow()) ren->GetRenderWindow()->Render(); } } } void Prop3D::ConnectInteractor(vtkRenderWindowInteractor *interactor) { } // ------------------------------------------------------ // // SERIALIZE DISPLAY PROPERTIES struct TransformProxy { Prop3DData* pd; template void serialize(Archive & ar, const unsigned int version) { ar & boost::serialization::make_nvp("Transform", pd->m_Transform); } }; struct AppearanceProxy { Prop3DData* pd; template void serialize(Archive & ar, const unsigned int version) { ar & boost::serialization::make_hrp("Color", pd->m_Color, "color"); ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity).range(0.0, 1.0).set_default(1.0); ar & boost::serialization::make_hrp_enum("Representation", pd->m_Representation, {"Points", "Wireframe", "Surface", "SurfaceWithEdges", "Volume", "Outline", "Slice"}); ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility); ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable); ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable); ar & boost::serialization::make_hrp("ShowBoundingBox", pd->m_ShowBoundingBox); ar & boost::serialization::make_hrp("ShowScaleMeasures", pd->m_ShowScaleMeasures); ar & boost::serialization::make_hrp_enum("HighlightMode", pd->m_HighlightMode, {"Plain", "Corners"}); } }; void Prop3D::serialize_display(Archive::display_properties_archive & ar, const unsigned int version) { AppearanceProxy appearance{pd}; ar & boost::serialization::make_nvp("Appearance", appearance); TransformProxy transform{pd}; ar & boost::serialization::make_nvp("Transform", transform); } } // namespace Vtk } // namespace uLib