Files
uLib/src/Vtk/uLibVtkInterface.cxx
2026-03-27 15:23:59 +00:00

654 lines
21 KiB
C++

/*//////////////////////////////////////////////////////////////////////////////
// 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 <vtkAutoInit.h>
#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string>
#include <vtkVersion.h>
#include "vtkViewport.h"
#include "uLibVtkInterface.h"
#include "Math/Transform.h"
#include <vtkActor.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkPropCollection.h>
#include <vtkProp3DCollection.h>
#include <vtkRendererCollection.h>
#include <vtkAssembly.h>
#include <vtkOutlineSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkCubeAxesActor.h>
#include <vtkRenderer.h>
#include <vtkProperty.h>
#include <vtkCamera.h>
#include <vtkPolyData.h>
#include <vtkFeatureEdges.h>
#include <vtkTransform.h>
#include <vtkRenderWindow.h>
#include "uLibVtkInterface.h"
#include "vtkHandlerWidget.h"
#include "Math/Dense.h"
#include "Vtk/Math/vtkDense.h"
#include "Core/Property.h"
namespace uLib {
namespace Vtk {
// PIMPL -------------------------------------------------------------------- //
class PuppetData {
public:
PuppetData() :
m_Renderers(vtkSmartPointer<vtkRendererCollection>::New()),
m_Assembly(vtkSmartPointer<vtkAssembly>::New()),
m_ShowBoundingBox(false),
m_ShowScaleMeasures(false),
m_Representation(Puppet::Surface),
m_Opacity(-1.0),
m_Selectable(true),
m_Selected(false),
m_Visibility(true),
m_Dragable(true)
{
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
}
~PuppetData() {
// No manual Delete needed for smart pointers
}
// members //
vtkSmartPointer<vtkRendererCollection> m_Renderers;
vtkSmartPointer<vtkAssembly> m_Assembly;
vtkSmartPointer<vtkOutlineSource> m_OutlineSource;
vtkSmartPointer<vtkActor> m_OutlineActor;
vtkSmartPointer<vtkCubeAxesActor> m_CubeAxesActor;
vtkSmartPointer<vtkActor> m_HighlightActor;
bool m_ShowBoundingBox;
bool m_ShowScaleMeasures;
int m_Representation;
double m_Color[3];
double m_Opacity;
bool m_Selectable;
bool m_Selected;
bool m_Visibility;
bool m_Dragable;
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) {
if (m_Representation == Puppet::SurfaceWithEdges) {
actor->GetProperty()->SetRepresentation(VTK_SURFACE);
actor->GetProperty()->SetEdgeVisibility(1);
} else {
actor->GetProperty()->SetRepresentation(m_Representation);
actor->GetProperty()->SetEdgeVisibility(0);
}
}
if (m_Color[0] != -1.0) {
actor->GetProperty()->SetColor(m_Color);
}
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();
parts->InitTraversal();
for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
this->ApplyAppearance(parts->GetNextProp3D());
}
}
}
void ApplyTransform(vtkProp3D* p3d) {
if (p3d) {
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());
p3d->SetUserMatrix(nullptr);
}
}
void UpdateHighlight() {
if (m_Selected) {
if (!m_HighlightActor) {
vtkSmartPointer<vtkFeatureEdges> edges = vtkSmartPointer<vtkFeatureEdges>::New();
edges->BoundaryEdgesOn();
edges->FeatureEdgesOn();
edges->SetFeatureAngle(30);
edges->NonManifoldEdgesOn();
edges->ManifoldEdgesOff();
// Find first polydata in assembly to highlight
vtkPropCollection *parts = m_Assembly->GetParts();
parts->InitTraversal();
for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
vtkActor *actor = vtkActor::SafeDownCast(parts->GetNextProp());
if (actor && actor->GetMapper() && actor->GetMapper()->GetDataSetInput()) {
edges->SetInputData(vtkPolyData::SafeDownCast(actor->GetMapper()->GetDataSetInput()));
break;
}
}
m_HighlightActor = vtkSmartPointer<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(edges->GetOutputPort());
m_HighlightActor->SetMapper(mapper);
m_HighlightActor->GetProperty()->SetColor(1.0, 0.5, 0.0); // Orange
m_HighlightActor->GetProperty()->SetLineWidth(2.0);
m_HighlightActor->GetProperty()->SetLighting(0);
}
// Update highlight matrix from the root prop
vtkProp3D* root = nullptr;
if (m_Assembly->GetParts()->GetNumberOfItems() == 1) {
root = vtkProp3D::SafeDownCast(m_Assembly->GetParts()->GetLastProp());
} else {
root = m_Assembly;
}
if (root) {
// Now that we use internal TRS, the prop's total matrix is GetMatrix()
m_HighlightActor->SetUserMatrix(root->GetMatrix());
}
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);
}
}
}
}
};
// -------------------------------------------------------------------------- //
Puppet::Puppet() : Object(), pd(new PuppetData) {
ULIB_ACTIVATE_DISPLAY_PROPERTIES;
for (auto* p : this->GetDisplayProperties()) {
uLib::Object::connect(p, &uLib::PropertyBase::Updated, this, &Puppet::Update);
}
}
Puppet::~Puppet()
{
delete pd;
}
vtkProp *Puppet::GetProp()
{
if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1)
return pd->m_Assembly->GetParts()->GetLastProp();
else
return pd->m_Assembly;
}
void Puppet::SetProp(vtkProp *prop)
{
if(prop) {
prop->SetPickable(pd->m_Selectable);
if (auto* p3d = vtkProp3D::SafeDownCast(prop)) {
pd->m_Assembly->AddPart(p3d);
}
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 (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1) {
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[0] < 0)
vp->GetColor(pd->m_Color);
}
}
}
}
void Puppet::RemoveProp(vtkProp *prop)
{
// TODO
}
void Puppet::ApplyAppearance(vtkProp* prop)
{
pd->ApplyAppearance(prop);
}
void Puppet::ApplyTransform(vtkProp3D* p3d)
{
pd->ApplyTransform(p3d);
}
vtkPropCollection *Puppet::GetParts()
{
return pd->m_Assembly->GetParts();
}
vtkPropCollection *Puppet::GetProps()
{
return pd->m_Assembly->GetParts();
}
void Puppet::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 Puppet::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 Puppet::AddToViewer(Viewport &viewer)
{
viewer.AddPuppet(*this);
}
void Puppet::RemoveFromViewer(Viewport &viewer)
{
viewer.RemovePuppet(*this);
}
vtkRendererCollection *Puppet::GetRenderers() const
{
return pd->m_Renderers;
}
void Puppet::PrintSelf(std::ostream &o) const
{
o << "Props Assembly: \n";
pd->m_Assembly->PrintSelf(o,vtkIndent(1));
o << "Connected Renderers: \n";
pd->m_Renderers->PrintSelf(o,vtkIndent(1));
}
void Puppet::ShowBoundingBox(bool show)
{
if (pd->m_ShowBoundingBox == show) return;
pd->m_ShowBoundingBox = show;
if (show) {
if (!pd->m_OutlineActor) {
pd->m_OutlineSource = vtkSmartPointer<vtkOutlineSource>::New();
pd->m_OutlineActor = vtkSmartPointer<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(pd->m_OutlineSource->GetOutputPort());
pd->m_OutlineActor->SetMapper(mapper);
pd->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
}
double* bounds = pd->m_Assembly->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 Puppet::ShowScaleMeasures(bool show)
{
if (pd->m_ShowScaleMeasures == show) return;
pd->m_ShowScaleMeasures = show;
if (show) {
if (!pd->m_CubeAxesActor) {
pd->m_CubeAxesActor = vtkSmartPointer<vtkCubeAxesActor>::New();
pd->m_CubeAxesActor->SetFlyModeToOuterEdges();
pd->m_CubeAxesActor->SetUseTextActor3D(1);
pd->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
}
double* bounds = pd->m_Assembly->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 Puppet::SetRepresentation(Representation mode)
{
pd->m_Representation = static_cast<int>(mode);
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
}
void Puppet::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 Puppet::SetColor(double r, double g, double b)
{
pd->m_Color[0] = r;
pd->m_Color[1] = g;
pd->m_Color[2] = b;
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
}
void Puppet::SetOpacity(double alpha)
{
pd->m_Opacity = alpha;
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
}
void Puppet::SetSelectable(bool selectable)
{
pd->m_Selectable = selectable;
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
props->GetNextProp3D()->SetPickable(selectable);
}
}
bool Puppet::IsSelectable() const
{
return pd->m_Selectable;
}
void Puppet::SetSelected(bool selected)
{
if (!pd->m_Selectable) return;
if (pd->m_Selected == selected) return;
pd->m_Selected = selected;
pd->UpdateHighlight();
}
bool Puppet::IsSelected() const
{
return pd->m_Selected;
}
void Puppet::Update()
{
vtkProp* root = this->GetProp();
if (root) {
// Handle transformation synchronization from content
if (auto* content = dynamic_cast<uLib::AffineTransform*>(GetContent())) {
pd->m_Transform = *content; // Uses TRS(const AffineTransform&)
}
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
pd->ApplyTransform(p3d);
}
pd->ApplyAppearance(root);
}
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
props->InitTraversal();
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
pd->ApplyAppearance(props->GetNextProp3D());
}
if (pd->m_Selected) {
pd->UpdateHighlight();
}
if (pd->m_ShowBoundingBox) {
double* bounds = pd->m_Assembly->GetBounds();
pd->m_OutlineSource->SetBounds(bounds);
pd->m_OutlineSource->Update();
}
if (pd->m_ShowScaleMeasures) {
double* bounds = pd->m_Assembly->GetBounds();
pd->m_CubeAxesActor->SetBounds(bounds);
}
// 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 Puppet::SyncFromVtk()
{
vtkProp* root = this->GetProp();
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
// Handle content synchronization if it's an AffineTransform
if (auto* content = dynamic_cast<uLib::AffineTransform*>(GetContent())) {
double pos[3], ori[3], scale[3];
p3d->GetPosition(pos);
p3d->GetOrientation(ori);
p3d->GetScale(scale);
// Convert VTK Degrees to Model Radians
content->SetPosition(Vector3f(pos[0], pos[1], pos[2]));
content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree);
content->SetScale(Vector3f(scale[0], scale[1], scale[2]));
// Re-sync internal puppet properties from the now-updated content
pd->m_Transform = *content;
}
else {
// Update internal puppet TRS directly from VTK components
double pos[3], ori[3], scale[3];
p3d->GetPosition(pos);
p3d->GetOrientation(ori);
p3d->GetScale(scale);
pd->m_Transform.position = Vector3f(pos[0], pos[1], pos[2]);
// Convert VTK Degrees to internal Radians
pd->m_Transform.rotation = Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree;
pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]);
}
// Notify puppet properties updated
if (auto* propPos = this->GetProperty("Position")) propPos->Updated();
if (auto* propOri = this->GetProperty("Orientation")) propOri->Updated();
if (auto* propScale = this->GetProperty("Scale")) propScale->Updated();
this->Object::Updated();
}
}
void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
{
}
struct TransformProxy {
PuppetData* pd;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_nvp("Transform", pd->m_Transform);
}
};
struct AppearanceProxy {
PuppetData* pd;
template<class Archive>
void serialize(Archive & ar, const unsigned int version) {
ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]);
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]);
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
ar & boost::serialization::make_hrp("Opacity", pd->m_Opacity);
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);
}
};
void Puppet::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);
}
void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }
void Puppet::serialize(Archive::xml_iarchive & ar, const unsigned int v) { }
void Puppet::serialize(Archive::text_oarchive & ar, const unsigned int v) { }
void Puppet::serialize(Archive::text_iarchive & ar, const unsigned int v) { }
void Puppet::serialize(Archive::hrt_oarchive & ar, const unsigned int v) { }
void Puppet::serialize(Archive::hrt_iarchive & ar, const unsigned int v) { }
void Puppet::serialize(Archive::log_archive & ar, const unsigned int v) { }
} // namespace Vtk
} // namespace uLib