Compare commits
10 Commits
a467b7385b
...
andrea-alg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
876b8f4592 | ||
|
|
ec2027e980 | ||
|
|
69b47623f8 | ||
|
|
f5c1e317e8 | ||
|
|
e0ffeff5b7 | ||
|
|
2c5d6842c3 | ||
|
|
422113a0e9 | ||
|
|
e4a8499104 | ||
|
|
6a65fe94c8 | ||
|
|
7d4acaef6d |
7
.agents/rules/micromamba.md
Normal file
7
.agents/rules/micromamba.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
|
||||
build in build directory using always micromamba "mutom" env.
|
||||
build with make flag -j$(nproc).
|
||||
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.vtk filter=lfs diff=lfs merge=lfs -text
|
||||
*.vti filter=lfs diff=lfs merge=lfs -text
|
||||
90
CLAUDE.md
Normal file
90
CLAUDE.md
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# Activate the conda environment (required before any build/run)
|
||||
export MAMBA_EXE="/home/share/micromamba/bin/micromamba"
|
||||
export MAMBA_ROOT_PREFIX="/home/share/micromamba"
|
||||
eval "$(/home/share/micromamba/bin/micromamba shell hook --shell bash)"
|
||||
micromamba activate mutom
|
||||
|
||||
# Configure (from repo root, using Conan preset)
|
||||
cmake --preset conan-release
|
||||
|
||||
# Build everything
|
||||
cmake --build build -j$(nproc)
|
||||
|
||||
# Build a specific target
|
||||
cmake --build build --target gcompose -j$(nproc)
|
||||
|
||||
# Run tests
|
||||
cmake --build build --target test
|
||||
# or
|
||||
ctest --test-dir build
|
||||
|
||||
# Run a single test binary (example)
|
||||
./build/src/Core/testing/CoreTest
|
||||
|
||||
# Run the gcompose GUI app
|
||||
./build/app/gcompose/gcompose
|
||||
```
|
||||
|
||||
First-time setup (if `build/` does not exist):
|
||||
```bash
|
||||
conan profile detect
|
||||
conan install . --output-folder=build --build=missing
|
||||
cmake --preset conan-release
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
**uLib** is a C++ framework for Cosmic Muon Tomography (CMT), structured as layered shared libraries:
|
||||
|
||||
```
|
||||
mutomCore → mutomMath → mutomDetectors → mutomGeant
|
||||
↘
|
||||
mutomVtk → gcompose (Qt6 GUI app)
|
||||
mutomRoot
|
||||
```
|
||||
|
||||
### Core Object Model (`src/Core/`)
|
||||
- All framework objects inherit from `uLib::Object`
|
||||
- **Property system**: `Property<T>` wraps member pointers with change notification via `PropertyChanged` signal
|
||||
- **Signal/slot**: `uLib::Object::connect(sender, &Sender::Signal, callback)` — resembles Qt but works for non-QObject classes
|
||||
- **Serialization**: Boost archives (`xml_oarchive`, `text_oarchive`, `hrt_oarchive`); `hrp<T>` marks fields as "human-readable properties"
|
||||
- `ObjectsContext` is a container owning a list of `Object*` pointers; signals `ObjectAdded`/`ObjectRemoved`
|
||||
|
||||
### VTK Layer (`src/Vtk/`)
|
||||
- `Puppet` (inherits `uLib::Object`): wraps a VTK `vtkProp` for rendering. Has `GetContent()` returning the underlying domain object. Display-only properties are registered via `ULIB_ACTIVATE_DISPLAY_PROPERTIES` macro.
|
||||
- `Viewport`: base class managing the VTK renderer, picking, selection logic. Maintains `m_Puppets` vector and `m_ObjectToPuppet` map.
|
||||
- `QViewport` (inherits `QWidget` + `Viewport`): Qt-embedded VTK widget. Emits Qt signal `puppetSelected(Puppet*)` on click-selection via `OnSelectionChanged`.
|
||||
- `vtkObjectsContext`: wraps `ObjectsContext`, creating/destroying `Puppet`s as objects come/go. Emits `PuppetAdded`/`PuppetRemoved`.
|
||||
- Display properties: `serialize_display()` + `display_properties_archive` registers selected `hrp<T>` fields as `PropertyBase*` in the puppet's `m_DisplayProperties`. `PropertyEditor::setObject(obj, displayOnly=true)` shows only those.
|
||||
|
||||
### gcompose GUI App (`app/gcompose/src/`)
|
||||
- `MainPanel`: top-level widget. Owns `ContextPanel` (left) and `ViewportPane` (right). Wires together viewport↔context selection via signals.
|
||||
- `ContextPanel`: tree view of `ObjectsContext`. Emits `objectSelected(Object*)`. Contains an embedded `PropertiesPanel`.
|
||||
- `PropertiesPanel`: shows `uLib::Object` properties via `PropertyEditor`.
|
||||
- `ViewportPane`: embeds `QViewport` + a slide-out "Display Properties" panel (`PropertyEditor` in display-only mode).
|
||||
- `PropertyEditor`: populates widgets from `Object::GetProperties()` (all) or `Puppet::GetDisplayProperties()` (display-only mode).
|
||||
|
||||
### Selection Sync Flow
|
||||
```
|
||||
Viewport click → Viewport::SelectPuppet() → QViewport::OnSelectionChanged()
|
||||
→ emit puppetSelected(p)
|
||||
→ MainPanel: contextPanel->selectObject(p->GetContent()) [updates tree + PropertiesPanel]
|
||||
→ MainPanel: firstPane->setObject(p) [updates Display Properties panel]
|
||||
|
||||
ContextPanel tree click → emit objectSelected(obj)
|
||||
→ MainPanel: viewport->SelectPuppet(puppet) [visual selection in VTK]
|
||||
→ MainPanel: firstPane->setObject(puppet) [updates Display Properties panel]
|
||||
```
|
||||
|
||||
### Key Patterns
|
||||
- **Two signal systems coexist**: Qt signals (`Q_OBJECT`, `connect(...)`) for GUI; `uLib::Object::connect(...)` for domain signals.
|
||||
- **Display properties** flow: `Puppet::serialize_display()` → `display_properties_archive` → `RegisterDisplayProperty()` → `PropertyEditor(displayOnly=true)`. Must call `ULIB_ACTIVATE_DISPLAY_PROPERTIES` in the puppet constructor.
|
||||
- **Puppet ↔ Object map**: `Viewport::m_ObjectToPuppet` allows lookup by domain object; `vtkObjectsContext::GetPuppet(obj)` does the same.
|
||||
@@ -84,6 +84,7 @@ macro(uLib_add_tests name)
|
||||
foreach(tn ${TESTS})
|
||||
add_executable(${tn} ${tn}.cpp)
|
||||
add_test(NAME ${tn} COMMAND ${tn})
|
||||
set_tests_properties(${tn} PROPERTIES ENVIRONMENT "CTEST_PROJECT_NAME=uLib;QT_QPA_PLATFORM=offscreen")
|
||||
|
||||
target_link_libraries(${tn} ${LIBRARIES})
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ endif()
|
||||
project(uLib)
|
||||
|
||||
# CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available.
|
||||
option(USE_CUDA "Enable CUDA support" ON)
|
||||
option(USE_CUDA "Enable CUDA support" OFF)
|
||||
if(USE_CUDA)
|
||||
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler")
|
||||
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr")
|
||||
@@ -103,6 +103,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_WARNING_OPTION}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -UULIB_SERIALIZATION_ON -Wno-cpp")
|
||||
|
||||
# CTEST framework
|
||||
set(CTEST_PROJECT_NAME "uLib")
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
|
||||
@@ -114,7 +115,7 @@ set(Boost_USE_MULTITHREADED ON)
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
message(STATUS "CMAKE_PREFIX_PATH is ${CMAKE_PREFIX_PATH}")
|
||||
|
||||
find_package(HDF5 REQUIRED CONFIG)
|
||||
find_package(HDF5 REQUIRED)
|
||||
|
||||
find_package(Boost 1.45.0 COMPONENTS program_options serialization unit_test_framework REQUIRED)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
1
Testing/Temporary/CTestCostData.txt
Normal file
1
Testing/Temporary/CTestCostData.txt
Normal file
@@ -0,0 +1 @@
|
||||
---
|
||||
3
Testing/Temporary/LastTest.log
Normal file
3
Testing/Temporary/LastTest.log
Normal file
@@ -0,0 +1,3 @@
|
||||
Start testing: Mar 25 18:59 UTC
|
||||
----------------------------------------------------------
|
||||
End testing: Mar 25 18:59 UTC
|
||||
@@ -14,10 +14,25 @@ void ContextModel::setContext(uLib::ObjectsContext* context) {
|
||||
beginResetModel();
|
||||
m_rootContext = context;
|
||||
if (m_rootContext) {
|
||||
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, [this]() {
|
||||
auto refresh = [this]() {
|
||||
this->beginResetModel();
|
||||
this->endResetModel();
|
||||
};
|
||||
|
||||
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh);
|
||||
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectAdded, [this, refresh](uLib::Object* obj) {
|
||||
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||
refresh();
|
||||
});
|
||||
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) {
|
||||
// Disconnect would be good here but not strictly required if refresh handles it
|
||||
refresh();
|
||||
});
|
||||
|
||||
// Connect existing objects
|
||||
for (auto* obj : m_rootContext->GetObjects()) {
|
||||
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
@@ -92,3 +92,27 @@ void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QIte
|
||||
emit objectSelected(target);
|
||||
m_propertiesPanel->setObject(target);
|
||||
}
|
||||
|
||||
void ContextPanel::selectObject(uLib::Object* obj) {
|
||||
if (!obj) {
|
||||
clearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_model->rowCount(); ++i) {
|
||||
QModelIndex idx = m_model->index(i, 0);
|
||||
if (idx.internalPointer() == obj) {
|
||||
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||
m_treeView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||
m_treeView->scrollTo(idx);
|
||||
m_propertiesPanel->setObject(obj); // Explicitly update properties too
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContextPanel::clearSelection() {
|
||||
QSignalBlocker blocker(m_treeView->selectionModel());
|
||||
m_treeView->selectionModel()->clearSelection();
|
||||
m_propertiesPanel->setObject(nullptr);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
~ContextPanel();
|
||||
|
||||
void setContext(uLib::ObjectsContext* context);
|
||||
void selectObject(uLib::Object* obj);
|
||||
void clearSelection();
|
||||
|
||||
signals:
|
||||
void objectSelected(uLib::Object* obj);
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include "StyleManager.h"
|
||||
#include "Math/VoxImage.h"
|
||||
|
||||
MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) {
|
||||
this->setObjectName("MainPanel");
|
||||
@@ -124,6 +127,17 @@ void MainPanel::setContext(uLib::ObjectsContext* context) {
|
||||
m_mainVtkContext = new uLib::Vtk::vtkObjectsContext(context);
|
||||
// viewport->AddPuppet(*m_mainVtkContext); // redundant
|
||||
|
||||
auto syncSelection = [this](uLib::Vtk::Puppet* p) {
|
||||
if (!p) {
|
||||
m_contextPanel->clearSelection();
|
||||
m_firstPane->setObject(nullptr);
|
||||
} else {
|
||||
m_contextPanel->selectObject(p->GetContent());
|
||||
m_firstPane->setObject(p);
|
||||
}
|
||||
};
|
||||
connect(viewport, &uLib::Vtk::QViewport::puppetSelected, syncSelection);
|
||||
|
||||
uLib::Object::connect(m_mainVtkContext, &uLib::Vtk::vtkObjectsContext::PuppetAdded, [this](uLib::Vtk::Puppet* p) {
|
||||
if (p) {
|
||||
auto panes = this->findChildren<ViewportPane*>();
|
||||
@@ -179,7 +193,35 @@ void MainPanel::onCreateObject(const std::string& className) {
|
||||
}
|
||||
|
||||
void MainPanel::onOpen() {
|
||||
// Placeholder for open logic
|
||||
QString fileName = QFileDialog::getOpenFileName(this, "Open File", "",
|
||||
"VTK/VTI Images (*.vtk *.vti);;All Files (*.*)");
|
||||
|
||||
if (fileName.isEmpty()) return;
|
||||
|
||||
QFileInfo info(fileName);
|
||||
QString ext = info.suffix().toLower();
|
||||
|
||||
if (ext == "vti" || ext == "vtk") {
|
||||
auto* obj = uLib::ObjectFactory::Instance().Create("VoxImage");
|
||||
auto* vox = dynamic_cast<uLib::Abstract::VoxImage*>(obj);
|
||||
if (vox) {
|
||||
bool success = false;
|
||||
if (ext == "vti") {
|
||||
success = vox->ImportFromVti(fileName.toStdString().c_str());
|
||||
} else {
|
||||
success = vox->ImportFromVtk(fileName.toStdString().c_str());
|
||||
}
|
||||
|
||||
if (success) {
|
||||
obj->SetInstanceName(info.fileName().toStdString());
|
||||
m_context->AddObject(obj);
|
||||
} else {
|
||||
delete obj;
|
||||
}
|
||||
} else {
|
||||
delete obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainPanel::onSave() {
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
#include <QSignalBlocker>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include "Vtk/uLibVtkInterface.h"
|
||||
#include "Math/Units.h"
|
||||
#include "Math/Dense.h"
|
||||
#include "Settings.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Qt {
|
||||
@@ -13,14 +16,29 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
|
||||
: QWidget(parent), m_BaseProperty(prop) {
|
||||
m_Layout = new QHBoxLayout(this);
|
||||
m_Layout->setContentsMargins(4, 2, 4, 2);
|
||||
m_Label = new QLabel(QString::fromStdString(prop->GetName()), this);
|
||||
m_Label->setMinimumWidth(100);
|
||||
|
||||
std::string unit = prop->GetUnits();
|
||||
QString labelText = QString::fromStdString(prop->GetName());
|
||||
if (!unit.empty()) {
|
||||
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||
if (!pref.empty()) {
|
||||
labelText += " [" + QString::fromStdString(pref) + "]";
|
||||
} else {
|
||||
labelText += " [" + QString::fromStdString(unit) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
m_Label = new QLabel(labelText, this);
|
||||
m_Label->setMinimumWidth(120);
|
||||
m_Layout->addWidget(m_Label);
|
||||
}
|
||||
PropertyWidgetBase::~PropertyWidgetBase() {}
|
||||
PropertyWidgetBase::~PropertyWidgetBase() {
|
||||
m_Connection.disconnect();
|
||||
}
|
||||
|
||||
// Helper for unit parsing
|
||||
static double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr) {
|
||||
double parseWithUnits(const QString& text, double* factorOut, QString* suffixOut) {
|
||||
static QRegularExpression re("^\\s*([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(_?[a-zA-Z]+)?\\s*$");
|
||||
QRegularExpressionMatch match = re.match(text);
|
||||
if (!match.hasMatch()) return 0.0;
|
||||
@@ -57,14 +75,23 @@ static double parseWithUnits(const QString& text, double* factorOut = nullptr, Q
|
||||
}
|
||||
|
||||
// UnitLineEdit implementation
|
||||
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix("mm"), m_IsInteger(false) {
|
||||
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix(""), m_IsInteger(false) {
|
||||
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
|
||||
}
|
||||
|
||||
void UnitLineEdit::setUnits(const QString& suffix, double factor) {
|
||||
m_Suffix = suffix;
|
||||
m_Factor = factor;
|
||||
updateText();
|
||||
}
|
||||
|
||||
void UnitLineEdit::setValue(double val) {
|
||||
if (m_Value != val) {
|
||||
m_Value = val;
|
||||
// Initial heuristic for unit if it was mm and value becomes large
|
||||
// Suffix heuristic ONLY if it was mm and no explicit unit was given?
|
||||
// Actually, if m_Suffix is empty or we have a specific one, we should respect it.
|
||||
// The original code had a heuristic, but it's better to let property decide.
|
||||
// Let's keep it ONLY if m_Suffix was mm (legacy behavior)
|
||||
if (!m_IsInteger && m_Suffix == "mm" && std::abs(val) >= 1000.0) { m_Suffix = "m"; m_Factor = CLHEP::meter; }
|
||||
updateText();
|
||||
}
|
||||
@@ -91,12 +118,18 @@ void UnitLineEdit::onEditingFinished() {
|
||||
|
||||
void UnitLineEdit::updateText() {
|
||||
QSignalBlocker blocker(this);
|
||||
QString s;
|
||||
if (m_IsInteger) {
|
||||
setText(QString::number((int)m_Value));
|
||||
s = QString::number((int)m_Value);
|
||||
if (s.isEmpty()) s = "0";
|
||||
} else {
|
||||
double displayVal = m_Value / m_Factor;
|
||||
setText(QString::number(displayVal, 'g', 6) + " " + m_Suffix);
|
||||
s = QString::number(displayVal, 'g', 6);
|
||||
if (!s.contains('.') && !s.contains('e')) {
|
||||
s += ".0";
|
||||
}
|
||||
}
|
||||
setText(s);
|
||||
}
|
||||
|
||||
void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
||||
@@ -107,10 +140,17 @@ void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
||||
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Edit = new UnitLineEdit(this);
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
std::string unit = prop->GetUnits();
|
||||
if (!unit.empty()) {
|
||||
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||
double factor = Settings::Instance().GetUnitFactor(pref);
|
||||
m_Edit->setUnits(QString::fromStdString(pref), factor);
|
||||
}
|
||||
m_Edit->setValue(prop->Get());
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
|
||||
uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
|
||||
m_Edit->setValue(m_Prop->Get());
|
||||
});
|
||||
}
|
||||
@@ -118,10 +158,17 @@ DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* pare
|
||||
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Edit = new UnitLineEdit(this);
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
std::string unit = prop->GetUnits();
|
||||
if (!unit.empty()) {
|
||||
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||
double factor = Settings::Instance().GetUnitFactor(pref);
|
||||
m_Edit->setUnits(QString::fromStdString(pref), factor);
|
||||
}
|
||||
m_Edit->setValue(prop->Get());
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
|
||||
uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
|
||||
m_Edit->setValue((double)m_Prop->Get());
|
||||
});
|
||||
}
|
||||
@@ -130,10 +177,17 @@ IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Edit = new UnitLineEdit(this);
|
||||
m_Edit->setIntegerOnly(true);
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
std::string unit = prop->GetUnits();
|
||||
if (!unit.empty()) {
|
||||
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||
double factor = Settings::Instance().GetUnitFactor(pref);
|
||||
m_Edit->setUnits(QString::fromStdString(pref), factor);
|
||||
}
|
||||
m_Edit->setValue(prop->Get());
|
||||
m_Layout->addWidget(m_Edit, 1);
|
||||
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
|
||||
uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
|
||||
m_Edit->setValue((double)m_Prop->Get());
|
||||
});
|
||||
}
|
||||
@@ -144,7 +198,7 @@ BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
|
||||
m_CheckBox->setChecked(prop->Get());
|
||||
m_Layout->addWidget(m_CheckBox, 1);
|
||||
connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
|
||||
uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
|
||||
if (m_CheckBox->isChecked() != m_Prop->Get()) {
|
||||
QSignalBlocker blocker(m_CheckBox);
|
||||
m_CheckBox->setChecked(m_Prop->Get());
|
||||
@@ -158,11 +212,11 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
|
||||
m_LineEdit = new QLineEdit(this);
|
||||
m_LineEdit->setText(QString::fromStdString(prop->Get()));
|
||||
m_Layout->addWidget(m_LineEdit, 1);
|
||||
connect(m_LineEdit, &QLineEdit::editingFinished, [this](){
|
||||
connect(m_LineEdit, &QLineEdit::editingFinished, [this](){
|
||||
std::string val = m_LineEdit->text().toStdString();
|
||||
if (m_Prop->Get() != val) m_Prop->Set(val);
|
||||
if (m_Prop->Get() != val) m_Prop->Set(val);
|
||||
});
|
||||
uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
|
||||
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
|
||||
QSignalBlocker blocker(m_LineEdit);
|
||||
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
|
||||
@@ -171,6 +225,57 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
|
||||
}
|
||||
StringPropertyWidget::~StringPropertyWidget() {}
|
||||
|
||||
class GroupHeaderWidget : public QWidget {
|
||||
public:
|
||||
GroupHeaderWidget(const QString& name, QWidget* parent = nullptr) : QWidget(parent) {
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 8, 0, 4);
|
||||
auto* line = new QFrame(this);
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
line->setStyleSheet("color: #555;");
|
||||
layout->addWidget(line);
|
||||
auto* label = new QLabel(name, this);
|
||||
QFont font = label->font();
|
||||
font.setBold(true);
|
||||
font.setPointSize(font.pointSize() + 1);
|
||||
label->setFont(font);
|
||||
label->setStyleSheet("color: #aaa; text-transform: uppercase;");
|
||||
layout->addWidget(label);
|
||||
}
|
||||
};
|
||||
|
||||
class EnumPropertyWidget : public PropertyWidgetBase {
|
||||
PropertyBase* m_Prop;
|
||||
QComboBox* m_Combo;
|
||||
public:
|
||||
EnumPropertyWidget(PropertyBase* prop, QWidget* parent)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
m_Combo = new QComboBox(this);
|
||||
const auto& labels = prop->GetEnumLabels();
|
||||
for (const auto& label : labels) {
|
||||
m_Combo->addItem(QString::fromStdString(label));
|
||||
}
|
||||
|
||||
// Get initial value
|
||||
if (auto* p = dynamic_cast<Property<int>*>(prop)) {
|
||||
m_Combo->setCurrentIndex(p->Get());
|
||||
connect(m_Combo, &QComboBox::currentIndexChanged, [p](int index){
|
||||
p->Set(index);
|
||||
});
|
||||
// Store connection in base m_Connection so it's auto-disconnected on destruction.
|
||||
m_Connection = uLib::Object::connect(p, &Property<int>::PropertyChanged, [this, p](){
|
||||
if (m_Combo->currentIndex() != p->Get()) {
|
||||
QSignalBlocker blocker(m_Combo);
|
||||
m_Combo->setCurrentIndex(p->Get());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
m_Layout->addWidget(m_Combo, 1);
|
||||
}
|
||||
};
|
||||
|
||||
PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr) {
|
||||
m_MainLayout = new QVBoxLayout(this);
|
||||
m_MainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -197,6 +302,11 @@ PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(null
|
||||
registerFactory<std::string>([](PropertyBase* p, QWidget* parent){
|
||||
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
|
||||
});
|
||||
|
||||
// Register EnumProperty specifically (needs to check type since it holds Property<int> but is EnumProperty)
|
||||
m_Factories[std::type_index(typeid(EnumProperty))] = [](PropertyBase* p, QWidget* parent) {
|
||||
return new EnumPropertyWidget(p, parent);
|
||||
};
|
||||
|
||||
// Vector Registration
|
||||
registerFactory<Vector2i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2i, 2>(static_cast<Property<Vector2i>*>(p), parent); });
|
||||
@@ -229,15 +339,51 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group properties by their group string
|
||||
std::map<std::string, std::vector<::uLib::PropertyBase*>> groupedProps;
|
||||
std::vector<std::string> groupOrder;
|
||||
|
||||
for (auto* prop : *props) {
|
||||
auto it = m_Factories.find(prop->GetTypeIndex());
|
||||
if (it != m_Factories.end()) {
|
||||
QWidget* widget = it->second(prop, m_Container);
|
||||
m_ContainerLayout->addWidget(widget);
|
||||
} else {
|
||||
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
|
||||
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
||||
m_ContainerLayout->addWidget(fallback);
|
||||
std::string group = prop->GetGroup();
|
||||
if (groupedProps.find(group) == groupedProps.end()) {
|
||||
groupOrder.push_back(group);
|
||||
}
|
||||
groupedProps[group].push_back(prop);
|
||||
}
|
||||
|
||||
for (const auto& groupName : groupOrder) {
|
||||
if (!groupName.empty()) {
|
||||
m_ContainerLayout->addWidget(new GroupHeaderWidget(QString::fromStdString(groupName), m_Container));
|
||||
}
|
||||
|
||||
for (auto* prop : groupedProps[groupName]) {
|
||||
QWidget* widget = nullptr;
|
||||
|
||||
// Priority 1: Check if it provides enum labels
|
||||
if (!prop->GetEnumLabels().empty()) {
|
||||
widget = new EnumPropertyWidget(prop, m_Container);
|
||||
} else {
|
||||
// Priority 2: Standard factory lookup
|
||||
auto it = m_Factories.find(prop->GetTypeIndex());
|
||||
if (it != m_Factories.end()) {
|
||||
widget = it->second(prop, m_Container);
|
||||
} else {
|
||||
// Debug info for unknown types
|
||||
std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName()
|
||||
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
|
||||
|
||||
widget = new PropertyWidgetBase(prop, m_Container);
|
||||
widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
if (widget) {
|
||||
if (!groupName.empty()) {
|
||||
// Indent grouped properties
|
||||
widget->setContentsMargins(16, 0, 0, 0);
|
||||
}
|
||||
m_ContainerLayout->addWidget(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_ContainerLayout->addStretch(1);
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
|
||||
#include "Core/Property.h"
|
||||
#include "Core/Object.h"
|
||||
#include "Core/Signal.h"
|
||||
#include "Math/Dense.h"
|
||||
#include "Settings.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Qt {
|
||||
|
||||
double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr);
|
||||
|
||||
class PropertyWidgetBase : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -30,6 +34,9 @@ protected:
|
||||
PropertyBase* m_BaseProperty;
|
||||
QHBoxLayout* m_Layout;
|
||||
QLabel* m_Label;
|
||||
// Stores the uLib signal connection so it can be disconnected on destruction,
|
||||
// preventing use-after-free when PropertyEditor::clear() deletes widgets.
|
||||
Connection m_Connection;
|
||||
};
|
||||
|
||||
class UnitLineEdit : public QLineEdit {
|
||||
@@ -37,6 +44,7 @@ class UnitLineEdit : public QLineEdit {
|
||||
public:
|
||||
UnitLineEdit(QWidget* parent = nullptr);
|
||||
void setValue(double val);
|
||||
void setUnits(const QString& suffix, double factor = 1.0);
|
||||
double getValue() const { return m_Value; }
|
||||
void setIntegerOnly(bool b);
|
||||
|
||||
@@ -86,11 +94,25 @@ class VectorPropertyWidget : public PropertyWidgetBase {
|
||||
public:
|
||||
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
|
||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||
|
||||
std::string unit = prop->GetUnits();
|
||||
double factor = 1.0;
|
||||
QString prefSuffix;
|
||||
if (!unit.empty()) {
|
||||
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||
factor = Settings::Instance().GetUnitFactor(pref);
|
||||
prefSuffix = QString::fromStdString(pref);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Size; ++i) {
|
||||
m_Edits[i] = new UnitLineEdit(this);
|
||||
if (std::is_integral<typename VecT::Scalar>::value) {
|
||||
m_Edits[i]->setIntegerOnly(true);
|
||||
}
|
||||
if (!prefSuffix.isEmpty()) {
|
||||
m_Edits[i]->setUnits(prefSuffix, factor);
|
||||
}
|
||||
m_Layout->addWidget(m_Edits[i], 1);
|
||||
|
||||
connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){
|
||||
@@ -100,10 +122,11 @@ public:
|
||||
});
|
||||
}
|
||||
updateEdits();
|
||||
uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
|
||||
m_Connection = uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
|
||||
updateEdits();
|
||||
});
|
||||
}
|
||||
~VectorPropertyWidget() { m_Connection.disconnect(); }
|
||||
|
||||
private:
|
||||
void updateEdits() {
|
||||
|
||||
75
app/gcompose/src/Settings.h
Normal file
75
app/gcompose/src/Settings.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef GCOMPOSE_SETTINGS_H
|
||||
#define GCOMPOSE_SETTINGS_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "Math/Units.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Qt {
|
||||
|
||||
class Settings {
|
||||
public:
|
||||
static Settings& Instance() {
|
||||
static Settings instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
enum Dimension {
|
||||
Length,
|
||||
Angle,
|
||||
Energy,
|
||||
Time,
|
||||
Dimensionless
|
||||
};
|
||||
|
||||
void SetPreferredUnit(Dimension dim, const std::string& unit) {
|
||||
m_PreferredUnits[dim] = unit;
|
||||
}
|
||||
|
||||
std::string GetPreferredUnit(Dimension dim) const {
|
||||
auto it = m_PreferredUnits.find(dim);
|
||||
if (it != m_PreferredUnits.end()) return it->second;
|
||||
|
||||
switch(dim) {
|
||||
case Length: return "mm";
|
||||
case Angle: return "deg";
|
||||
case Energy: return "MeV";
|
||||
case Time: return "ns";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
double GetUnitFactor(const std::string& unit) const {
|
||||
if (unit == "m") return CLHEP::meter;
|
||||
if (unit == "cm") return CLHEP::centimeter;
|
||||
if (unit == "mm") return CLHEP::millimeter;
|
||||
if (unit == "um") return CLHEP::micrometer;
|
||||
if (unit == "deg") return CLHEP::degree;
|
||||
if (unit == "rad") return CLHEP::radian;
|
||||
if (unit == "ns") return CLHEP::nanosecond;
|
||||
if (unit == "s") return CLHEP::second;
|
||||
if (unit == "ms") return CLHEP::millisecond;
|
||||
if (unit == "MeV") return CLHEP::megaelectronvolt;
|
||||
if (unit == "GeV") return CLHEP::gigaelectronvolt;
|
||||
if (unit == "eV") return CLHEP::electronvolt;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Dimension IdentifyDimension(const std::string& unit) const {
|
||||
if (unit == "m" || unit == "cm" || unit == "mm" || unit == "um" || unit == "nm") return Length;
|
||||
if (unit == "deg" || unit == "rad") return Angle;
|
||||
if (unit == "MeV" || unit == "GeV" || unit == "eV" || unit == "keV" || unit == "TeV") return Energy;
|
||||
if (unit == "ns" || unit == "s" || unit == "ms" || unit == "us") return Time;
|
||||
return Dimensionless;
|
||||
}
|
||||
|
||||
private:
|
||||
Settings() {}
|
||||
std::map<Dimension, std::string> m_PreferredUnits;
|
||||
};
|
||||
|
||||
} // namespace Qt
|
||||
} // namespace uLib
|
||||
|
||||
#endif
|
||||
@@ -46,23 +46,25 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
|
||||
|
||||
m_layout->addWidget(m_titleBar);
|
||||
|
||||
// Main horizontal container for viewport and display panel
|
||||
QWidget* mainArea = new QWidget(this);
|
||||
QHBoxLayout* hLayout = new QHBoxLayout(mainArea);
|
||||
hLayout->setContentsMargins(0, 0, 0, 0);
|
||||
hLayout->setSpacing(0);
|
||||
m_layout->addWidget(mainArea);
|
||||
// Main area with splitter for viewport and display panel
|
||||
m_areaSplitter = new QSplitter(Qt::Horizontal, this);
|
||||
m_areaSplitter->setObjectName("ViewportAreaSplitter");
|
||||
m_layout->addWidget(m_areaSplitter, 1);
|
||||
|
||||
// Viewport will be added here via setViewport
|
||||
m_viewport = new uLib::Vtk::QViewport(mainArea);
|
||||
hLayout->addWidget(m_viewport);
|
||||
m_viewport = new uLib::Vtk::QViewport(m_areaSplitter);
|
||||
m_areaSplitter->addWidget(m_viewport);
|
||||
|
||||
// Display Panel (Overlay/Slide-out)
|
||||
m_displayPanel = new QFrame(mainArea);
|
||||
m_displayPanel = new QFrame(m_areaSplitter);
|
||||
m_displayPanel->setObjectName("DisplayPropertiesPanel");
|
||||
m_displayPanel->setFixedWidth(250);
|
||||
m_displayPanel->setMinimumWidth(150);
|
||||
m_displayPanel->hide();
|
||||
|
||||
m_areaSplitter->addWidget(m_displayPanel);
|
||||
m_areaSplitter->setStretchFactor(0, 1);
|
||||
m_areaSplitter->setStretchFactor(1, 0);
|
||||
|
||||
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
|
||||
panelLayout->setContentsMargins(5, 5, 5, 5);
|
||||
|
||||
@@ -72,8 +74,6 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
|
||||
|
||||
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
|
||||
panelLayout->addWidget(m_displayEditor);
|
||||
|
||||
hLayout->addWidget(m_displayPanel);
|
||||
|
||||
connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel);
|
||||
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu);
|
||||
@@ -85,7 +85,15 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
|
||||
ViewportPane::~ViewportPane() {}
|
||||
|
||||
void ViewportPane::toggleDisplayPanel() {
|
||||
m_displayPanel->setVisible(m_toggleBtn->isChecked());
|
||||
bool visible = m_toggleBtn->isChecked();
|
||||
m_displayPanel->setVisible(visible);
|
||||
if (visible && m_areaSplitter->sizes().value(1, 0) == 0) {
|
||||
QList<int> sizes = m_areaSplitter->sizes();
|
||||
int total = sizes[0] + sizes[1];
|
||||
sizes[1] = 250;
|
||||
sizes[0] = total - 250;
|
||||
m_areaSplitter->setSizes(sizes);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewportPane::setObject(uLib::Object* obj) {
|
||||
@@ -107,15 +115,14 @@ void ViewportPane::setObject(uLib::Object* obj) {
|
||||
|
||||
void ViewportPane::setViewport(QWidget* viewport, const QString& title) {
|
||||
if (m_viewport) {
|
||||
m_viewport->parentWidget()->layout()->removeWidget(m_viewport);
|
||||
delete m_viewport;
|
||||
}
|
||||
m_viewport = viewport;
|
||||
m_titleLabel->setText(title);
|
||||
|
||||
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
auto* mainAreaLayout = static_cast<QHBoxLayout*>(m_displayPanel->parentWidget()->layout());
|
||||
mainAreaLayout->insertWidget(0, m_viewport);
|
||||
m_areaSplitter->insertWidget(0, m_viewport);
|
||||
m_areaSplitter->setStretchFactor(0, 1);
|
||||
}
|
||||
|
||||
void ViewportPane::addVtkViewport() {
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace uLib {
|
||||
namespace Qt { class PropertyEditor; }
|
||||
}
|
||||
|
||||
class QSplitter;
|
||||
class QVBoxLayout;
|
||||
class QLabel;
|
||||
|
||||
@@ -39,6 +40,7 @@ private:
|
||||
QVBoxLayout* m_layout;
|
||||
QWidget* m_titleBar;
|
||||
QLabel* m_titleLabel;
|
||||
QSplitter* m_areaSplitter;
|
||||
QWidget* m_viewport;
|
||||
|
||||
// Display Properties Overlay
|
||||
|
||||
BIN
assets/exmaples/vtk/2026_03_24_C1_Prod11_test_img_40_trim55505_scale1.00_sigma1.0.vtk
(Stored with Git LFS)
Normal file
BIN
assets/exmaples/vtk/2026_03_24_C1_Prod11_test_img_40_trim55505_scale1.00_sigma1.0.vtk
(Stored with Git LFS)
Normal file
Binary file not shown.
338
docs/algorithms/algoritm.md
Normal file
338
docs/algorithms/algoritm.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Algorithm Infrastructure
|
||||
|
||||
## Overview
|
||||
|
||||
An algorithm in the uLib infrastructure is a class for containing a functional that can be dynamically loaded into memory as a plug-in.
|
||||
It derives from the base `Object` class (`Core/Object.h`) and therefore can contain properties that define the serialization of operating parameters or the implementation of widgets for interactive parameter manipulation.
|
||||
|
||||
The algorithm class is designed to be inserted into an `AlgorithmTask`, a class for managing the execution of scheduled operations. A task contains `Run` and `Stop` methods to start and stop execution. A task can be configured to work in two modes:
|
||||
|
||||
- **Cyclic mode**: the algorithm is executed periodically with a configurable cycle time.
|
||||
- **Asynchronous mode**: the task waits for a trigger before each execution. Triggers can come from the uLib signal-slot system (`Object::connect`) or from a condition variable as defined in the monitor pattern (`Core/Monitor.h`).
|
||||
|
||||
The algorithm is defined as a template class on two types `T_enc` and `T_dec`. The encoder is a type for data input or another algorithm that is chained with this one and outputs data in a compatible format. The decoder is the type of data output or a downstream algorithm compatible with it.
|
||||
|
||||
## Class Hierarchy
|
||||
|
||||
```
|
||||
Object (Core/Object.h)
|
||||
|
|
||||
+-- Algorithm<T_enc, T_dec> (Core/Algorithm.h)
|
||||
| |
|
||||
| +-- VoxImageFilter<VoxelT, CrtpImplT> (Math/VoxImageFilter.h)
|
||||
| |
|
||||
| +-- VoxFilterAlgorithmLinear (Math/VoxImageFilterLinear.hpp)
|
||||
| +-- VoxFilterAlgorithmMedian (Math/VoxImageFilterMedian.hpp)
|
||||
| +-- VoxFilterAlgorithmAbtrim (Math/VoxImageFilterABTrim.hpp)
|
||||
| +-- VoxFilterAlgorithmSPR (Math/VoxImageFilterABTrim.hpp)
|
||||
| +-- VoxFilterAlgorithmThreshold (Math/VoxImageFilterThreshold.hpp)
|
||||
| +-- VoxFilterAlgorithmBilateral (Math/VoxImageFilterBilateral.hpp)
|
||||
| +-- VoxFilterAlgorithmBilateralTrim(Math/VoxImageFilterBilateral.hpp)
|
||||
| +-- VoxFilterAlgorithm2ndStat (Math/VoxImageFilter2ndStat.hpp)
|
||||
| +-- VoxFilterAlgorithmCustom (Math/VoxImageFilterCustom.hpp)
|
||||
|
|
||||
+-- Thread (Core/Threads.h)
|
||||
|
|
||||
+-- AlgorithmTask<T_enc, T_dec> (Core/Algorithm.h)
|
||||
```
|
||||
|
||||
## Algorithm (`Core/Algorithm.h`)
|
||||
|
||||
### Template Parameters
|
||||
|
||||
```cpp
|
||||
template <typename T_enc, typename T_dec>
|
||||
class Algorithm : public Object;
|
||||
```
|
||||
|
||||
- **`T_enc`** (Encoder): the input data type. Can be a raw data type or a pointer to a data structure. When chaining algorithms, the upstream algorithm's `T_dec` must be compatible with this algorithm's `T_enc`.
|
||||
- **`T_dec`** (Decoder): the output data type. Produced by `Process()` and consumed by the next algorithm in the chain.
|
||||
|
||||
### Core Interface
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `virtual T_dec Process(const T_enc& input) = 0` | Pure virtual. Implement the algorithm logic here. |
|
||||
| `T_dec operator()(const T_enc& input)` | Calls `Process()`. Enables functional syntax: `result = alg(data)`. |
|
||||
|
||||
### Algorithm Chaining
|
||||
|
||||
Algorithms can be linked in processing pipelines via encoder/decoder pointers:
|
||||
|
||||
```cpp
|
||||
Algorithm* upstream; // SetEncoder() / GetEncoder()
|
||||
Algorithm* downstream; // SetDecoder() / GetDecoder()
|
||||
```
|
||||
|
||||
This allows building chains like:
|
||||
|
||||
```
|
||||
[RawData] --> AlgorithmA --> AlgorithmB --> [Result]
|
||||
encoder decoder
|
||||
```
|
||||
|
||||
### Signals
|
||||
|
||||
| Signal | Emitted when |
|
||||
|--------|-------------|
|
||||
| `Started()` | The algorithm begins processing (caller responsibility). |
|
||||
| `Finished()` | The algorithm completes processing (caller responsibility). |
|
||||
|
||||
### Device Preference (CUDA)
|
||||
|
||||
Algorithms report their preferred execution device via `GetPreferredDevice()`:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `virtual MemoryDevice GetPreferredDevice() const` | Returns `RAM` or `VRAM`. Subclasses override. |
|
||||
| `void SetPreferredDevice(MemoryDevice dev)` | Manually set the device preference. |
|
||||
| `bool IsGPU() const` | Shorthand for `GetPreferredDevice() == VRAM`. |
|
||||
|
||||
GPU-based algorithms are responsible for calling `cudaDeviceSynchronize()` inside their `Process()` implementation before returning, so that results are available to the caller or downstream algorithm.
|
||||
|
||||
### Example: Defining a Custom Algorithm
|
||||
|
||||
```cpp
|
||||
class MyFilter : public Algorithm<VoxImage<Voxel>*, VoxImage<Voxel>*> {
|
||||
public:
|
||||
const char* GetClassName() const override { return "MyFilter"; }
|
||||
|
||||
VoxImage<Voxel>* Process(VoxImage<Voxel>* const& image) override {
|
||||
// ... filter the image in-place ...
|
||||
return image;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## AlgorithmTask (`Core/Algorithm.h`)
|
||||
|
||||
`AlgorithmTask` manages the execution of an `Algorithm` within a scheduled, threaded context. It inherits from `Thread` (`Core/Threads.h`) and uses `Mutex` (`Core/Monitor.h`) for synchronization.
|
||||
|
||||
### Template Parameters
|
||||
|
||||
```cpp
|
||||
template <typename T_enc, typename T_dec>
|
||||
class AlgorithmTask : public Thread;
|
||||
```
|
||||
|
||||
Must match the `Algorithm<T_enc, T_dec>` it manages.
|
||||
|
||||
### Configuration
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `void SetAlgorithm(AlgorithmType* alg)` | Set the algorithm to execute. |
|
||||
| `void SetMode(Mode mode)` | `Cyclic` or `Async`. |
|
||||
| `void SetCycleTime(int ms)` | Period for cyclic mode (milliseconds). |
|
||||
|
||||
### Execution Modes
|
||||
|
||||
#### Cyclic Mode
|
||||
|
||||
The algorithm's `Process()` is called periodically. The cycle waits on a `condition_variable_any` with timeout, so `Stop()` can interrupt immediately without waiting for the full cycle.
|
||||
|
||||
```cpp
|
||||
AlgorithmTask<int, int> task;
|
||||
task.SetAlgorithm(&myAlgorithm);
|
||||
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||
task.SetCycleTime(100); // every 100ms
|
||||
task.Run(inputData);
|
||||
// ... later ...
|
||||
task.Stop();
|
||||
```
|
||||
|
||||
#### Asynchronous Mode
|
||||
|
||||
The task thread blocks on a condition variable until `Notify()` is called. Each notification triggers exactly one `Process()` invocation.
|
||||
|
||||
```cpp
|
||||
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||
task.Run(inputData);
|
||||
|
||||
// Trigger manually:
|
||||
task.Notify();
|
||||
|
||||
// Or connect to a signal:
|
||||
task.ConnectTrigger(sender, &SenderClass::DataReady);
|
||||
// Now each emission of DataReady() triggers one Process() call.
|
||||
```
|
||||
|
||||
### Lifecycle
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `void Run(const T_enc& input)` | Starts the background thread with the given input. |
|
||||
| `void Stop()` | Requests stop and joins the thread. |
|
||||
| `bool IsRunning()` | Inherited from `Thread`. |
|
||||
|
||||
### Signals
|
||||
|
||||
| Signal | Emitted when |
|
||||
|--------|-------------|
|
||||
| `Stopped()` | The task thread has completed (after last `Process()` and before thread exit). |
|
||||
|
||||
### Signal-Slot Triggering
|
||||
|
||||
`ConnectTrigger()` connects any uLib `Object` signal to the task's `Notify()` method:
|
||||
|
||||
```cpp
|
||||
task.ConnectTrigger(detector, &Detector::EventReady);
|
||||
```
|
||||
|
||||
This uses the uLib signal system (`Core/Signal.h`), not Qt signals. The connection is type-safe and works with the `Object::connect` infrastructure.
|
||||
|
||||
## VoxImageFilter (`Math/VoxImageFilter.h`)
|
||||
|
||||
`VoxImageFilter` specializes `Algorithm` for kernel-based volumetric image filtering. It uses CRTP (Curiously Recurring Template Pattern) so that concrete filters provide their `Evaluate()` method without virtual dispatch overhead in the inner loop.
|
||||
|
||||
### Template Parameters
|
||||
|
||||
```cpp
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
class VoxImageFilter : public Abstract::VoxImageFilter,
|
||||
public Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*>;
|
||||
```
|
||||
|
||||
- **`VoxelT`**: the voxel data type (must satisfy `Interface::Voxel` — requires `.Value` and `.Count` fields).
|
||||
- **`CrtpImplT`**: the concrete filter subclass. Must implement:
|
||||
```cpp
|
||||
float Evaluate(const VoxImage<VoxelT>& buffer, int index);
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. `Process(image)` creates a read-only buffer copy of the input image.
|
||||
2. For each voxel in parallel (OpenMP), it calls `CrtpImplT::Evaluate(buffer, index)`.
|
||||
3. `Evaluate()` reads from the buffer using the kernel offsets and writes the result.
|
||||
4. The filtered image is returned (in-place modification).
|
||||
|
||||
```
|
||||
Process(image)
|
||||
|
|
||||
+-- buffer = copy of image (read-only snapshot)
|
||||
|
|
||||
+-- #pragma omp parallel for
|
||||
| for each voxel i:
|
||||
| image[i].Value = CrtpImplT::Evaluate(buffer, i)
|
||||
|
|
||||
+-- return image
|
||||
```
|
||||
|
||||
### Kernel System
|
||||
|
||||
The `Kernel<VoxelT>` class stores convolution weights and precomputed index offsets:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `SetKernelNumericXZY(values)` | Set kernel weights from a flat vector (XZY order). |
|
||||
| `SetKernelSpherical(shape)` | Set weights via a radial function `f(distance^2)`. |
|
||||
| `SetKernelWeightFunction(shape)` | Set weights via a 3D position function `f(Vector3f)`. |
|
||||
|
||||
### CUDA Support
|
||||
|
||||
Concrete filters can override `Process()` with a CUDA implementation:
|
||||
|
||||
```cpp
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||
if (this->GetPreferredDevice() == MemoryDevice::VRAM) {
|
||||
// Launch CUDA kernel, synchronize, return
|
||||
} else {
|
||||
return BaseClass::Process(image); // CPU fallback
|
||||
}
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
The base class `GetPreferredDevice()` automatically returns `VRAM` when the image or kernel data resides on the GPU, enabling transparent device dispatch.
|
||||
|
||||
Filters with CUDA implementations: `VoxFilterAlgorithmLinear`, `VoxFilterAlgorithmAbtrim`, `VoxFilterAlgorithmSPR`.
|
||||
|
||||
### Concrete Filters
|
||||
|
||||
| Filter | File | Description |
|
||||
|--------|------|-------------|
|
||||
| `VoxFilterAlgorithmLinear` | `VoxImageFilterLinear.hpp` | Weighted linear convolution (FIR filter). CUDA-enabled. |
|
||||
| `VoxFilterAlgorithmMedian` | `VoxImageFilterMedian.hpp` | Median filter with kernel-weighted sorting. |
|
||||
| `VoxFilterAlgorithmAbtrim` | `VoxImageFilterABTrim.hpp` | Alpha-beta trimmed mean filter. CUDA-enabled. |
|
||||
| `VoxFilterAlgorithmSPR` | `VoxImageFilterABTrim.hpp` | Robespierre filter: trimmed mean applied only to outlier voxels. CUDA-enabled. |
|
||||
| `VoxFilterAlgorithmThreshold` | `VoxImageFilterThreshold.hpp` | Binary threshold filter. |
|
||||
| `VoxFilterAlgorithmBilateral` | `VoxImageFilterBilateral.hpp` | Edge-preserving bilateral filter (intensity-weighted Gaussian). |
|
||||
| `VoxFilterAlgorithmBilateralTrim` | `VoxImageFilterBilateral.hpp` | Bilateral filter with alpha-beta trimming. |
|
||||
| `VoxFilterAlgorithm2ndStat` | `VoxImageFilter2ndStat.hpp` | Local variance (second-order statistic). |
|
||||
| `VoxFilterAlgorithmCustom` | `VoxImageFilterCustom.hpp` | User-supplied evaluation function via function pointer. |
|
||||
|
||||
### Example: Using a Filter with AlgorithmTask
|
||||
|
||||
```cpp
|
||||
// Create filter and configure kernel
|
||||
VoxFilterAlgorithmLinear<Voxel> filter(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights(27, 1.0f); // uniform 3x3x3
|
||||
filter.SetKernelNumericXZY(weights);
|
||||
|
||||
// Direct use
|
||||
filter.SetImage(&image);
|
||||
filter.Run();
|
||||
|
||||
// Or via Algorithm interface
|
||||
VoxImage<Voxel>* result = filter.Process(&image);
|
||||
|
||||
// Or scheduled in a task
|
||||
AlgorithmTask<VoxImage<Voxel>*, VoxImage<Voxel>*> task;
|
||||
task.SetAlgorithm(&filter);
|
||||
task.SetMode(AlgorithmTask<VoxImage<Voxel>*, VoxImage<Voxel>*>::Cyclic);
|
||||
task.SetCycleTime(500);
|
||||
task.Run(&image);
|
||||
```
|
||||
|
||||
## Structural Benefits
|
||||
|
||||
### 1. Uniform Processing Interface
|
||||
|
||||
Every algorithm — from a simple threshold to a GPU-accelerated convolution — exposes the same `Process(input) -> output` interface. Client code does not need to know the concrete type:
|
||||
|
||||
```cpp
|
||||
Algorithm<VoxImage<Voxel>*, VoxImage<Voxel>*>* alg = &anyFilter;
|
||||
alg->Process(&image);
|
||||
```
|
||||
|
||||
### 2. Pipeline Composition
|
||||
|
||||
The encoder/decoder chaining allows building data processing pipelines where each stage transforms data and passes it to the next. Type safety is enforced at compile time through template parameters.
|
||||
|
||||
### 3. Scheduled and Event-Driven Execution
|
||||
|
||||
`AlgorithmTask` decouples the algorithm from its execution schedule. The same algorithm can be:
|
||||
- Called directly (`Process()`)
|
||||
- Run periodically (Cyclic mode for monitoring/acquisition)
|
||||
- Triggered by events (Async mode for reactive processing)
|
||||
|
||||
### 4. Transparent CPU/GPU Dispatch
|
||||
|
||||
The `MemoryDevice` preference and `GetPreferredDevice()` virtual allow the same algorithm interface to dispatch to CPU or GPU implementations. The `DataAllocator` transparently manages RAM/VRAM transfers, and concrete filters override `Process()` with CUDA kernels when data is on the GPU.
|
||||
|
||||
### 5. Integration with the Object System
|
||||
|
||||
Since `Algorithm` inherits from `Object`, algorithms gain:
|
||||
- **Properties**: serializable parameters via the `Property<T>` system, enabling persistent configuration and GUI widget generation.
|
||||
- **Signals**: `Started`/`Finished` notifications for connecting to monitoring or logging.
|
||||
- **Serialization**: save/load algorithm configuration via Boost archives.
|
||||
- **Instance naming**: `SetInstanceName()` for runtime identification in contexts.
|
||||
|
||||
### 6. CRTP Performance for Inner Loops
|
||||
|
||||
`VoxImageFilter` uses CRTP to dispatch to `Evaluate()` without virtual function overhead. The per-voxel evaluation runs at full speed inside OpenMP parallel loops, while the outer `Process()` method remains virtual for polymorphic use through the Algorithm interface.
|
||||
|
||||
## Dependencies
|
||||
|
||||
```
|
||||
Core/Object.h — base class, properties, signals, serialization
|
||||
Core/Signal.h — signal-slot connection infrastructure
|
||||
Core/Monitor.h — Mutex, condition variables, ULIB_MUTEX_LOCK
|
||||
Core/Threads.h — Thread base class for AlgorithmTask
|
||||
Core/DataAllocator.h — MemoryDevice enum, RAM/VRAM data management
|
||||
Math/VoxImage.h — volumetric image container
|
||||
Math/VoxImageFilter.h — kernel-based filter framework
|
||||
```
|
||||
|
||||
|
||||
263
src/Core/Algorithm.h
Normal file
263
src/Core/Algorithm.h
Normal file
@@ -0,0 +1,263 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef U_CORE_ALGORITHM_H
|
||||
#define U_CORE_ALGORITHM_H
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "Core/Object.h"
|
||||
#include "Core/Monitor.h"
|
||||
#include "Core/Threads.h"
|
||||
#include "Core/DataAllocator.h"
|
||||
|
||||
namespace uLib {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// ALGORITHM /////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Algorithm is a template class for containing a functional that can be
|
||||
* dynamically loaded as a plug-in. It derives from Object and supports
|
||||
* properties for serialization and interactive parameter widgets.
|
||||
*
|
||||
* Algorithms are responsible for their own GPU synchronization: if Process()
|
||||
* launches CUDA kernels, it must call cudaDeviceSynchronize() before returning
|
||||
* so that the result is available to the caller or downstream algorithm.
|
||||
*
|
||||
* @tparam T_enc Encoder type: the input data type, or a chained algorithm
|
||||
* whose output is compatible with this algorithm's input.
|
||||
* @tparam T_dec Decoder type: the output data type, or a chained algorithm
|
||||
* whose input is compatible with this algorithm's output.
|
||||
*/
|
||||
template <typename T_enc, typename T_dec>
|
||||
class Algorithm : public Object {
|
||||
public:
|
||||
using EncoderType = T_enc;
|
||||
using DecoderType = T_dec;
|
||||
|
||||
Algorithm()
|
||||
: Object()
|
||||
, m_Encoder(nullptr)
|
||||
, m_Decoder(nullptr)
|
||||
, m_PreferredDevice(MemoryDevice::RAM)
|
||||
{}
|
||||
virtual ~Algorithm() = default;
|
||||
|
||||
virtual const char* GetClassName() const override { return "Algorithm"; }
|
||||
|
||||
// Processing ///////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Process input data and produce output.
|
||||
* Override this in subclasses to implement the algorithm logic.
|
||||
* GPU-based implementations must synchronize before returning.
|
||||
*/
|
||||
virtual T_dec Process(const T_enc& input) = 0;
|
||||
|
||||
/** @brief Operator form of Process for functional chaining. */
|
||||
T_dec operator()(const T_enc& input) { return Process(input); }
|
||||
|
||||
// Chaining /////////////////////////////////////////////////////////////////
|
||||
|
||||
void SetEncoder(Algorithm* enc) { m_Encoder = enc; }
|
||||
Algorithm* GetEncoder() const { return m_Encoder; }
|
||||
|
||||
void SetDecoder(Algorithm* dec) { m_Decoder = dec; }
|
||||
Algorithm* GetDecoder() const { return m_Decoder; }
|
||||
|
||||
// Device preference ////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Returns the preferred memory device for this algorithm.
|
||||
* CUDA-capable algorithms should override to return VRAM when their
|
||||
* data resides on the GPU.
|
||||
*/
|
||||
virtual MemoryDevice GetPreferredDevice() const { return m_PreferredDevice; }
|
||||
void SetPreferredDevice(MemoryDevice dev) { m_PreferredDevice = dev; }
|
||||
|
||||
/** @brief Returns true if this algorithm prefers GPU execution. */
|
||||
bool IsGPU() const { return GetPreferredDevice() == MemoryDevice::VRAM; }
|
||||
|
||||
// Signals //////////////////////////////////////////////////////////////////
|
||||
|
||||
signals:
|
||||
virtual void Started() { ULIB_SIGNAL_EMIT(Algorithm::Started); }
|
||||
virtual void Finished() { ULIB_SIGNAL_EMIT(Algorithm::Finished); }
|
||||
|
||||
protected:
|
||||
Algorithm* m_Encoder;
|
||||
Algorithm* m_Decoder;
|
||||
MemoryDevice m_PreferredDevice;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// ALGORITHM TASK ////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief AlgorithmTask manages the execution of an Algorithm within a
|
||||
* scheduled context. Uses uLib::Thread for execution and uLib::Mutex for
|
||||
* synchronization.
|
||||
*
|
||||
* Two execution modes:
|
||||
* - Cyclic: executes Process() periodically with configurable cycle time.
|
||||
* - Async: waits for Notify() or a connected signal before each execution.
|
||||
*
|
||||
* GPU synchronization is the algorithm's responsibility (see Algorithm::Process).
|
||||
*/
|
||||
template <typename T_enc, typename T_dec>
|
||||
class AlgorithmTask : public Thread {
|
||||
public:
|
||||
using AlgorithmType = Algorithm<T_enc, T_dec>;
|
||||
|
||||
enum Mode { Cyclic, Async };
|
||||
|
||||
AlgorithmTask()
|
||||
: Thread()
|
||||
, m_Algorithm(nullptr)
|
||||
, m_Mode(Cyclic)
|
||||
, m_CycleTime_ms(1000)
|
||||
, m_StopRequested(false)
|
||||
, m_Triggered(false)
|
||||
{}
|
||||
|
||||
virtual ~AlgorithmTask() { Stop(); }
|
||||
|
||||
virtual const char* GetClassName() const override { return "AlgorithmTask"; }
|
||||
|
||||
// Configuration ////////////////////////////////////////////////////////////
|
||||
|
||||
void SetAlgorithm(AlgorithmType* alg) { m_Algorithm = alg; }
|
||||
AlgorithmType* GetAlgorithm() const { return m_Algorithm; }
|
||||
|
||||
void SetMode(Mode mode) { m_Mode = mode; }
|
||||
Mode GetMode() const { return m_Mode; }
|
||||
|
||||
void SetCycleTime(int milliseconds) { m_CycleTime_ms = milliseconds; }
|
||||
int GetCycleTime() const { return m_CycleTime_ms; }
|
||||
|
||||
// Lifecycle ////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Start the task execution in a separate thread (via Thread::Start).
|
||||
* In Cyclic mode, the algorithm is executed periodically.
|
||||
* In Async mode, call Notify() or connect a signal to trigger execution.
|
||||
*/
|
||||
void Run(const T_enc& input) {
|
||||
if (IsRunning()) return;
|
||||
m_StopRequested.store(false);
|
||||
m_Triggered.store(false);
|
||||
m_Input = input;
|
||||
Start();
|
||||
}
|
||||
|
||||
/** @brief Stop the task execution and join the thread. */
|
||||
void Stop() {
|
||||
m_StopRequested.store(true);
|
||||
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
|
||||
m_Condition.notify_all();
|
||||
}
|
||||
if (IsJoinable()) Join();
|
||||
}
|
||||
|
||||
// Async triggering /////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Notify the task to execute one iteration (Async mode).
|
||||
* Can be called from a signal-slot connection or externally.
|
||||
*/
|
||||
void Notify() {
|
||||
m_Triggered.store(true);
|
||||
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
|
||||
m_Condition.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect an Object signal to trigger async execution.
|
||||
* Usage: task.ConnectTrigger(sender, &SenderClass::SomeSignal);
|
||||
*/
|
||||
template <typename Func1>
|
||||
Connection ConnectTrigger(typename FunctionPointer<Func1>::Object* sender, Func1 sigf) {
|
||||
return Object::connect(sender, sigf, [this]() { Notify(); });
|
||||
}
|
||||
|
||||
// Signals //////////////////////////////////////////////////////////////////
|
||||
|
||||
signals:
|
||||
virtual void Stopped() { ULIB_SIGNAL_EMIT(AlgorithmTask::Stopped); }
|
||||
|
||||
protected:
|
||||
/** @brief Thread entry point — dispatches to cyclic or async loop. */
|
||||
void Run() override {
|
||||
if (m_Mode == Cyclic)
|
||||
RunCyclic();
|
||||
else
|
||||
RunAsync();
|
||||
Stopped();
|
||||
}
|
||||
|
||||
private:
|
||||
void RunCyclic() {
|
||||
while (!m_StopRequested.load()) {
|
||||
if (m_Algorithm) m_Algorithm->Process(m_Input);
|
||||
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
|
||||
m_Condition.wait_for(lock,
|
||||
std::chrono::milliseconds(m_CycleTime_ms),
|
||||
[this]() { return m_StopRequested.load(); });
|
||||
}
|
||||
}
|
||||
|
||||
void RunAsync() {
|
||||
while (!m_StopRequested.load()) {
|
||||
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
|
||||
m_Condition.wait(lock, [this]() {
|
||||
return m_StopRequested.load() || m_Triggered.load();
|
||||
});
|
||||
if (m_StopRequested.load()) break;
|
||||
m_Triggered.store(false);
|
||||
if (m_Algorithm) m_Algorithm->Process(m_Input);
|
||||
}
|
||||
}
|
||||
|
||||
AlgorithmType* m_Algorithm;
|
||||
Mode m_Mode;
|
||||
int m_CycleTime_ms;
|
||||
T_enc m_Input;
|
||||
|
||||
std::atomic<bool> m_StopRequested;
|
||||
std::atomic<bool> m_Triggered;
|
||||
Mutex m_WaitMutex;
|
||||
std::condition_variable_any m_Condition;
|
||||
};
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_CORE_ALGORITHM_H
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
set(HEADERS
|
||||
Archives.h
|
||||
Array.h
|
||||
set(HEADERS
|
||||
Algorithm.h
|
||||
Archives.h
|
||||
Array.h
|
||||
Collection.h
|
||||
DataAllocator.h
|
||||
Debug.h
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
else
|
||||
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
||||
}
|
||||
// std::cout << "DataAllocator Constructor: ptr=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||
}
|
||||
|
||||
DataAllocator(const DataAllocator<T> &other)
|
||||
@@ -63,7 +64,12 @@ public:
|
||||
m_RamData = new T[m_Size];
|
||||
else
|
||||
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
||||
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
||||
|
||||
if (m_OwnsObjects) {
|
||||
std::copy(other.m_RamData, other.m_RamData + m_Size, m_RamData);
|
||||
} else {
|
||||
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
||||
}
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
if (other.m_VramData) {
|
||||
@@ -73,14 +79,17 @@ public:
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// std::cout << "DataAllocator CopyConstructor: from=" << other.m_RamData << " to=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||
}
|
||||
|
||||
~DataAllocator() {
|
||||
// std::cout << "DataAllocator Destructor: ptr=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||
if (m_RamData) {
|
||||
if (m_OwnsObjects)
|
||||
delete[] m_RamData;
|
||||
else
|
||||
::operator delete(m_RamData);
|
||||
m_RamData = nullptr;
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
if (m_VramData) {
|
||||
@@ -91,6 +100,13 @@ public:
|
||||
|
||||
DataAllocator &operator=(const DataAllocator &other) {
|
||||
if (this != &other) {
|
||||
if (m_Size == other.m_Size && m_OwnsObjects != other.m_OwnsObjects) {
|
||||
// Ownership changed but size is same: we must force reallocation
|
||||
// to avoid using the wrong delete operator later.
|
||||
size_t oldSize = m_Size;
|
||||
m_Size = 0;
|
||||
resize(oldSize); // This will free the old buffer with the OLD ownership
|
||||
}
|
||||
m_OwnsObjects = other.m_OwnsObjects;
|
||||
resize(other.m_Size);
|
||||
m_Device = other.m_Device;
|
||||
@@ -101,7 +117,11 @@ public:
|
||||
else
|
||||
m_RamData = static_cast<T *>(::operator new(m_Size * sizeof(T)));
|
||||
}
|
||||
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
||||
if (m_OwnsObjects) {
|
||||
std::copy(other.m_RamData, other.m_RamData + m_Size, m_RamData);
|
||||
} else {
|
||||
std::memcpy(m_RamData, other.m_RamData, m_Size * sizeof(T));
|
||||
}
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
if (other.m_VramData) {
|
||||
@@ -112,6 +132,7 @@ public:
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// std::cout << "DataAllocator AssigmentOp: otherPtr=" << other.m_RamData << " thisPtr=" << m_RamData << " size=" << m_Size << " own=" << m_OwnsObjects << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -152,6 +173,8 @@ public:
|
||||
if (m_Size == size)
|
||||
return;
|
||||
|
||||
// std::cout << "DataAllocator Resize: from=" << m_Size << " to=" << size << " ptr=" << m_RamData << " own=" << m_OwnsObjects << std::endl;
|
||||
|
||||
T *newRam = nullptr;
|
||||
T *newVram = nullptr;
|
||||
|
||||
@@ -162,7 +185,11 @@ public:
|
||||
newRam = static_cast<T *>(::operator new(size * sizeof(T)));
|
||||
|
||||
if (m_RamData) {
|
||||
std::memcpy(newRam, m_RamData, std::min(m_Size, size) * sizeof(T));
|
||||
if (m_OwnsObjects) {
|
||||
std::copy(m_RamData, m_RamData + std::min(m_Size, size), newRam);
|
||||
} else {
|
||||
std::memcpy(newRam, m_RamData, std::min(m_Size, size) * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
@@ -61,8 +61,8 @@ public:
|
||||
};
|
||||
|
||||
std::string m_InstanceName;
|
||||
Vector<Signal> sigv;
|
||||
Vector<Slot> slov;
|
||||
std::vector<Signal> sigv;
|
||||
std::vector<Slot> slov;
|
||||
std::vector<PropertyBase*> m_Properties;
|
||||
std::vector<PropertyBase*> m_DynamicProperties;
|
||||
bool m_SignalsBlocked;
|
||||
@@ -70,14 +70,17 @@ public:
|
||||
|
||||
// Implementations of Property methods
|
||||
void Object::RegisterProperty(PropertyBase* prop) {
|
||||
if (prop) d->m_Properties.push_back(prop);
|
||||
if (prop) {
|
||||
d->m_Properties.push_back(prop);
|
||||
}
|
||||
}
|
||||
|
||||
void Object::RegisterDynamicProperty(PropertyBase* prop) {
|
||||
if (prop) {
|
||||
for (auto* existing : d->m_DynamicProperties) {
|
||||
if (existing == prop) return;
|
||||
}
|
||||
d->m_DynamicProperties.push_back(prop);
|
||||
// Note: prop already added itself to m_Properties
|
||||
// during its own constructor call to owner->RegisterProperty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +88,16 @@ const std::vector<PropertyBase*>& Object::GetProperties() const {
|
||||
return d->m_Properties;
|
||||
}
|
||||
|
||||
PropertyBase* Object::GetProperty(const std::string& name) const {
|
||||
for (auto* p : d->m_Properties) {
|
||||
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
|
||||
}
|
||||
for (auto* p : d->m_DynamicProperties) {
|
||||
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// In Object.h, the template serialize needs to be updated to call property serialization.
|
||||
// However, since Object::serialize is a template in the header, we might need a helper here.
|
||||
|
||||
@@ -125,6 +138,18 @@ Object::Object(const Object ©) : d(new ObjectPrivate) {
|
||||
}
|
||||
}
|
||||
|
||||
Object& Object::operator=(const Object &other) {
|
||||
// Intentionally does NOT share 'd'. Each Object owns its own ObjectPrivate.
|
||||
// Without this, the compiler-generated operator= would copy the 'd' pointer,
|
||||
// causing two objects to share the same ObjectPrivate. When both are
|
||||
// destroyed, 'd' would be deleted twice, corrupting the heap.
|
||||
if (this != &other && other.d) {
|
||||
d->m_InstanceName = other.d->m_InstanceName;
|
||||
d->m_SignalsBlocked = other.d->m_SignalsBlocked;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Object::~Object() {
|
||||
for (auto* p : d->m_DynamicProperties) {
|
||||
delete p;
|
||||
@@ -135,6 +160,7 @@ Object::~Object() {
|
||||
void Object::DeepCopy(const Object ©) {
|
||||
if (this == ©) return;
|
||||
if (copy.d) d->m_InstanceName = copy.d->m_InstanceName;
|
||||
std::cout << "Object DeepCopy: from d=" << copy.d << " to d=" << d << std::endl;
|
||||
// Note: signals, slots and properties are intentionally not copied
|
||||
// to maintain instance uniquely and avoid duplicate registrations.
|
||||
this->Updated();
|
||||
@@ -161,9 +187,8 @@ void Object::LoadConfig(std::istream &is, int version) {
|
||||
|
||||
void Object::PrintSelf(std::ostream &o) const {
|
||||
o << "OBJECT signals: ------------------\n";
|
||||
Vector<ObjectPrivate::Signal>::Iterator itr;
|
||||
for (itr = d->sigv.begin(); itr < d->sigv.end(); itr++) {
|
||||
o << " signal:[ " << itr->sigstr << " ]\n";
|
||||
for (const auto& sig : d->sigv) {
|
||||
o << " signal:[ " << sig.sigstr << " ]\n";
|
||||
}
|
||||
o << "--------------------------------------\n\n";
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
|
||||
Object();
|
||||
Object(const Object ©);
|
||||
~Object();
|
||||
virtual ~Object();
|
||||
|
||||
virtual const char * GetClassName() const { return "Object"; }
|
||||
|
||||
@@ -93,6 +93,7 @@ public:
|
||||
void RegisterProperty(PropertyBase* prop);
|
||||
void RegisterDynamicProperty(PropertyBase* prop);
|
||||
const std::vector<PropertyBase*>& GetProperties() const;
|
||||
PropertyBase* GetProperty(const std::string& name) const;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// PARAMETERS //
|
||||
@@ -227,10 +228,7 @@ public:
|
||||
|
||||
void PrintSelf(std::ostream &o) const;
|
||||
|
||||
inline const Object &operator=(const Object ©) {
|
||||
this->DeepCopy(copy);
|
||||
return *this;
|
||||
}
|
||||
Object &operator=(const Object &other);
|
||||
|
||||
private:
|
||||
bool addSignalImpl(SignalBase *sig, GenericMFPtr fptr, const char *name);
|
||||
|
||||
@@ -20,13 +20,13 @@ public:
|
||||
* @brief Adds an object to the context.
|
||||
* @param obj Pointer to the object to add.
|
||||
*/
|
||||
void AddObject(Object* obj);
|
||||
virtual void AddObject(Object* obj);
|
||||
|
||||
/**
|
||||
* @brief Removes an object from the context.
|
||||
* @param obj Pointer to the object to remove.
|
||||
*/
|
||||
void RemoveObject(Object* obj);
|
||||
virtual void RemoveObject(Object* obj);
|
||||
|
||||
/**
|
||||
* @brief Clears all objects from the context.
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
#define U_CORE_PROPERTY_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <typeinfo>
|
||||
#include <typeindex> // Added
|
||||
#include <boost/serialization/nvp.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <vector>
|
||||
#include <boost/type_traits/is_class.hpp>
|
||||
#include <boost/mpl/bool.hpp>
|
||||
#include <boost/serialization/serialization.hpp>
|
||||
#include "Core/Archives.h"
|
||||
#include "Core/Signal.h"
|
||||
#include "Core/Object.h"
|
||||
@@ -23,6 +28,18 @@ public:
|
||||
virtual const char* GetTypeName() const = 0;
|
||||
virtual std::string GetValueAsString() const = 0;
|
||||
virtual std::type_index GetTypeIndex() const = 0; // Added
|
||||
virtual const std::string& GetUnits() const = 0;
|
||||
virtual void SetUnits(const std::string& units) = 0;
|
||||
virtual const std::vector<std::string>& GetEnumLabels() const {
|
||||
static std::vector<std::string> empty;
|
||||
return empty;
|
||||
}
|
||||
virtual const std::string& GetGroup() const = 0;
|
||||
virtual void SetGroup(const std::string& group) = 0;
|
||||
std::string GetQualifiedName() const {
|
||||
if (GetGroup().empty()) return GetName();
|
||||
return GetGroup() + "." + GetName();
|
||||
}
|
||||
|
||||
// Signal support
|
||||
signals:
|
||||
@@ -45,16 +62,16 @@ template <typename T>
|
||||
class Property : public PropertyBase {
|
||||
public:
|
||||
// PROXY: Use an existing variable as back-end storage
|
||||
Property(Object* owner, const std::string& name, T* valuePtr)
|
||||
: m_owner(owner), m_name(name), m_value(valuePtr), m_own(false) {
|
||||
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "")
|
||||
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false) {
|
||||
if (m_owner) {
|
||||
m_owner->RegisterProperty(this);
|
||||
}
|
||||
}
|
||||
|
||||
// MANAGED: Create and own internal storage
|
||||
Property(Object* owner, const std::string& name, const T& defaultValue = T())
|
||||
: m_owner(owner), m_name(name), m_value(new T(defaultValue)), m_own(true) {
|
||||
Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "")
|
||||
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true) {
|
||||
if (m_owner) {
|
||||
m_owner->RegisterProperty(this);
|
||||
}
|
||||
@@ -68,6 +85,10 @@ public:
|
||||
virtual const std::string& GetName() const override { return m_name; }
|
||||
virtual const char* GetTypeName() const override { return typeid(T).name(); }
|
||||
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
|
||||
virtual const std::string& GetUnits() const override { return m_units; }
|
||||
virtual void SetUnits(const std::string& units) override { m_units = units; }
|
||||
virtual const std::string& GetGroup() const override { return m_group; }
|
||||
virtual void SetGroup(const std::string& group) override { m_group = group; }
|
||||
|
||||
|
||||
std::string GetValueAsString() const override {
|
||||
@@ -118,6 +139,8 @@ public:
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string m_units;
|
||||
std::string m_group;
|
||||
T* m_value;
|
||||
bool m_own;
|
||||
Object* m_owner;
|
||||
@@ -135,6 +158,22 @@ typedef Property<float> FloatProperty;
|
||||
typedef Property<double> DoubleProperty;
|
||||
typedef Property<Bool_t> BoolProperty;
|
||||
|
||||
/**
|
||||
* @brief Property specialized for enumerations, providing labels for GUI representations.
|
||||
*/
|
||||
class EnumProperty : public Property<int> {
|
||||
public:
|
||||
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "", const std::string& group = "")
|
||||
: Property<int>(owner, name, valuePtr, units, group), m_Labels(labels) {}
|
||||
|
||||
const std::vector<std::string>& GetEnumLabels() const override { return m_Labels; }
|
||||
const char* GetTypeName() const override { return "Enum"; }
|
||||
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(EnumProperty)); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_Labels;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Macro to simplify property declaration within a class.
|
||||
* Usage: ULIB_PROPERTY(float, Width, 1.0f)
|
||||
@@ -184,25 +223,53 @@ public:
|
||||
boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header),
|
||||
m_Object(obj) {}
|
||||
|
||||
std::string GetCurrentGroup() const {
|
||||
std::string group;
|
||||
for (const auto& g : m_GroupStack) {
|
||||
if (!group.empty()) group += ".";
|
||||
group += g;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
// Core logic: encounter HRP -> Create Dynamic Property
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp<T> &t) {
|
||||
if (m_Object) {
|
||||
// We use const_cast because we are just creating a proxy to the member
|
||||
m_Object->RegisterDynamicProperty(
|
||||
new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value())
|
||||
);
|
||||
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
if (m_Object) {
|
||||
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
m_Object->RegisterDynamicProperty(p);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle standard NVPs by recursing (important for base classes)
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::nvp<T> &t) {
|
||||
boost::archive::detail::common_oarchive<property_register_archive>::save_override(t.const_value());
|
||||
if (t.name()) m_GroupStack.push_back(t.name());
|
||||
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
||||
if (t.name()) m_GroupStack.pop_back();
|
||||
}
|
||||
|
||||
// Ignore everything else
|
||||
template<class T> void save_override(const T &t) {}
|
||||
// Recursion for nested classes, ignore primitives
|
||||
template<class T>
|
||||
void save_override(const T &t) {
|
||||
this->save_helper(t, typename boost::is_class<T>::type());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::true_) {
|
||||
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::false_) {}
|
||||
|
||||
// Required attribute overrides for common_oarchive
|
||||
void save_override(const boost::archive::object_id_type & t) {}
|
||||
@@ -213,6 +280,9 @@ public:
|
||||
void save_override(const boost::archive::class_id_reference_type & t) {}
|
||||
void save_override(const boost::archive::class_name_type & t) {}
|
||||
void save_override(const boost::archive::tracking_type & t) {}
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_GroupStack;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,12 +75,14 @@ template <class T> struct access2 {};
|
||||
template <class T>
|
||||
class hrp : public boost::serialization::wrapper_traits<hrp<T>> {
|
||||
const char *m_name;
|
||||
const char *m_units;
|
||||
T &m_value;
|
||||
|
||||
public:
|
||||
explicit hrp(const char *name_, T &t) : m_name(name_), m_value(t) {}
|
||||
explicit hrp(const char *name_, T &t, const char* units_ = nullptr) : m_name(name_), m_units(units_), m_value(t) {}
|
||||
|
||||
const char *name() const { return this->m_name; }
|
||||
const char *units() const { return this->m_units; }
|
||||
T &value() { return this->m_value; }
|
||||
const T &const_value() const { return this->m_value; }
|
||||
|
||||
@@ -98,11 +100,46 @@ public:
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline hrp<T> make_hrp(const char *name, T &t) {
|
||||
return hrp<T>(name, t);
|
||||
inline hrp<T> make_hrp(const char *name, T &t, const char* units = nullptr) {
|
||||
return hrp<T>(name, t, units);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class hrp_enum : public boost::serialization::wrapper_traits<hrp_enum<T>> {
|
||||
const char *m_name;
|
||||
const char *m_units;
|
||||
T &m_value;
|
||||
std::vector<std::string> m_labels;
|
||||
|
||||
public:
|
||||
explicit hrp_enum(const char *name_, T &t, const std::vector<std::string>& labels, const char* units_ = nullptr)
|
||||
: m_name(name_), m_units(units_), m_value(t), m_labels(labels) {}
|
||||
|
||||
const char *name() const { return this->m_name; }
|
||||
const char *units() const { return this->m_units; }
|
||||
T &value() { return this->m_value; }
|
||||
const std::vector<std::string>& labels() const { return m_labels; }
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
|
||||
template <class Archivex>
|
||||
void save(Archivex &ar, const unsigned int /* version */) const {
|
||||
ar << boost::serialization::make_nvp(m_name, m_value);
|
||||
}
|
||||
|
||||
template <class Archivex>
|
||||
void load(Archivex &ar, const unsigned int /* version */) {
|
||||
ar >> boost::serialization::make_nvp(m_name, m_value);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
inline hrp_enum<T> make_hrp_enum(const char *name, T &t, const std::vector<std::string>& labels, const char* units = nullptr) {
|
||||
return hrp_enum<T>(name, t, labels, units);
|
||||
}
|
||||
|
||||
#define HRP(name) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name)
|
||||
#define HRPU(name, units) boost::serialization::make_hrp(BOOST_PP_STRINGIZE(name), name, units)
|
||||
|
||||
} // namespace serialization
|
||||
} // namespace boost
|
||||
|
||||
206
src/Core/testing/AlgorithmTest.cpp
Normal file
206
src/Core/testing/AlgorithmTest.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#include "Core/Algorithm.h"
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
|
||||
using namespace uLib;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Test algorithms
|
||||
|
||||
class DoubleAlgorithm : public Algorithm<int, int> {
|
||||
public:
|
||||
const char* GetClassName() const override { return "DoubleAlgorithm"; }
|
||||
int Process(const int& input) override {
|
||||
m_CallCount++;
|
||||
return input * 2;
|
||||
}
|
||||
std::atomic<int> m_CallCount{0};
|
||||
};
|
||||
|
||||
class StringifyAlgorithm : public Algorithm<int, std::string> {
|
||||
public:
|
||||
const char* GetClassName() const override { return "StringifyAlgorithm"; }
|
||||
std::string Process(const int& input) override {
|
||||
return std::to_string(input);
|
||||
}
|
||||
};
|
||||
|
||||
// Signal source to test ConnectTrigger
|
||||
class TriggerSource : public Object {
|
||||
public:
|
||||
const char* GetClassName() const override { return "TriggerSource"; }
|
||||
signals:
|
||||
virtual void DataReady() { ULIB_SIGNAL_EMIT(TriggerSource::DataReady); }
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Tests
|
||||
|
||||
void TestBasicProcess() {
|
||||
std::cout << "Testing basic Algorithm::Process..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
assert(alg.Process(5) == 10);
|
||||
assert(alg.Process(-3) == -6);
|
||||
assert(alg.Process(0) == 0);
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestOperatorCall() {
|
||||
std::cout << "Testing Algorithm::operator()..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
assert(alg(7) == 14);
|
||||
assert(alg(0) == 0);
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestEncoderDecoderChain() {
|
||||
std::cout << "Testing encoder/decoder chain pointers..." << std::endl;
|
||||
DoubleAlgorithm a, b;
|
||||
a.SetDecoder(&b);
|
||||
b.SetEncoder(&a);
|
||||
assert(a.GetDecoder() == &b);
|
||||
assert(b.GetEncoder() == &a);
|
||||
assert(a.GetEncoder() == nullptr);
|
||||
assert(b.GetDecoder() == nullptr);
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestAlgorithmSignals() {
|
||||
std::cout << "Testing Algorithm signals..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
bool started = false;
|
||||
bool finished = false;
|
||||
Object::connect(&alg, &DoubleAlgorithm::Started, [&]() { started = true; });
|
||||
Object::connect(&alg, &DoubleAlgorithm::Finished, [&]() { finished = true; });
|
||||
alg.Started();
|
||||
alg.Finished();
|
||||
assert(started);
|
||||
assert(finished);
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestCyclicTask() {
|
||||
std::cout << "Testing AlgorithmTask cyclic mode (Thread-based)..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
AlgorithmTask<int, int> task;
|
||||
task.SetAlgorithm(&alg);
|
||||
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||
task.SetCycleTime(50);
|
||||
|
||||
assert(!task.IsRunning());
|
||||
task.Run(5);
|
||||
|
||||
// Let it run for ~200ms -> expect ~4 cycles
|
||||
Thread::Sleep(220);
|
||||
task.Stop();
|
||||
|
||||
assert(!task.IsRunning());
|
||||
int count = alg.m_CallCount.load();
|
||||
std::cout << " Cyclic iterations: " << count << std::endl;
|
||||
assert(count >= 3 && count <= 6);
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestAsyncTask() {
|
||||
std::cout << "Testing AlgorithmTask async mode (Mutex + condition_variable)..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
AlgorithmTask<int, int> task;
|
||||
task.SetAlgorithm(&alg);
|
||||
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||
|
||||
task.Run(42);
|
||||
Thread::Sleep(50); // let the thread start and wait
|
||||
|
||||
// Trigger 3 notifications
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
task.Notify();
|
||||
Thread::Sleep(30);
|
||||
}
|
||||
|
||||
task.Stop();
|
||||
int count = alg.m_CallCount.load();
|
||||
std::cout << " Async invocations: " << count << std::endl;
|
||||
assert(count == 3);
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestConnectTrigger() {
|
||||
std::cout << "Testing AlgorithmTask::ConnectTrigger (signal-slot async)..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
AlgorithmTask<int, int> task;
|
||||
task.SetAlgorithm(&alg);
|
||||
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||
|
||||
TriggerSource source;
|
||||
task.ConnectTrigger(&source, &TriggerSource::DataReady);
|
||||
|
||||
task.Run(10);
|
||||
Thread::Sleep(50);
|
||||
|
||||
// Emit signal 3 times
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
source.DataReady();
|
||||
Thread::Sleep(30);
|
||||
}
|
||||
|
||||
task.Stop();
|
||||
int count = alg.m_CallCount.load();
|
||||
std::cout << " Signal-triggered invocations: " << count << std::endl;
|
||||
assert(count == 3);
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestTaskStoppedSignal() {
|
||||
std::cout << "Testing AlgorithmTask Stopped signal..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
AlgorithmTask<int, int> task;
|
||||
task.SetAlgorithm(&alg);
|
||||
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||
task.SetCycleTime(20);
|
||||
|
||||
std::atomic<bool> stopped{false};
|
||||
Object::connect(&task, &AlgorithmTask<int, int>::Stopped,
|
||||
[&]() { stopped.store(true); });
|
||||
|
||||
task.Run(1);
|
||||
Thread::Sleep(50);
|
||||
task.Stop();
|
||||
Thread::Sleep(50);
|
||||
|
||||
assert(stopped.load());
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestClassName() {
|
||||
std::cout << "Testing GetClassName..." << std::endl;
|
||||
DoubleAlgorithm alg;
|
||||
AlgorithmTask<int, int> task;
|
||||
assert(std::string(alg.GetClassName()) == "DoubleAlgorithm");
|
||||
assert(std::string(task.GetClassName()) == "AlgorithmTask");
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
void TestDifferentTypes() {
|
||||
std::cout << "Testing Algorithm with different enc/dec types..." << std::endl;
|
||||
StringifyAlgorithm alg;
|
||||
assert(alg.Process(42) == "42");
|
||||
assert(alg.Process(-1) == "-1");
|
||||
assert(alg(100) == "100");
|
||||
std::cout << " Passed." << std::endl;
|
||||
}
|
||||
|
||||
int main() {
|
||||
TestBasicProcess();
|
||||
TestOperatorCall();
|
||||
TestEncoderDecoderChain();
|
||||
TestAlgorithmSignals();
|
||||
TestDifferentTypes();
|
||||
TestCyclicTask();
|
||||
TestAsyncTask();
|
||||
TestConnectTrigger();
|
||||
TestTaskStoppedSignal();
|
||||
TestClassName();
|
||||
std::cout << "All Algorithm tests passed!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -23,11 +23,13 @@ set( TESTS
|
||||
VectorMetaAllocatorTest
|
||||
PropertyTypesTest
|
||||
HRPTest
|
||||
PropertyGroupingTest
|
||||
MutexTest
|
||||
ThreadsTest
|
||||
OpenMPTest
|
||||
TeamTest
|
||||
AffinityTest
|
||||
AlgorithmTest
|
||||
)
|
||||
|
||||
set(LIBRARIES
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Core/Monitor.h"
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
78
src/Core/testing/PropertyGroupingTest.cpp
Normal file
78
src/Core/testing/PropertyGroupingTest.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include "Core/Object.h"
|
||||
#include "Core/Property.h"
|
||||
|
||||
using namespace uLib;
|
||||
|
||||
struct Nested {
|
||||
float x = 1.0f;
|
||||
float y = 2.0f;
|
||||
|
||||
ULIB_SERIALIZE_ACCESS
|
||||
template<class Archive>
|
||||
void serialize(Archive & ar, const unsigned int version) {
|
||||
ar & HRP(x);
|
||||
ar & HRP(y);
|
||||
}
|
||||
};
|
||||
|
||||
class GroupObject : public Object {
|
||||
uLibTypeMacro(GroupObject, Object)
|
||||
public:
|
||||
Nested position;
|
||||
Nested orientation;
|
||||
float weight = 50.0f;
|
||||
|
||||
ULIB_SERIALIZE_ACCESS
|
||||
template<class Archive>
|
||||
void serialize(Archive & ar, const unsigned int version) {
|
||||
ar & boost::serialization::make_nvp("Position", position);
|
||||
ar & boost::serialization::make_nvp("Orientation", orientation);
|
||||
ar & HRP(weight);
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
std::cout << "Testing Property Grouping..." << std::endl;
|
||||
|
||||
GroupObject obj;
|
||||
ULIB_ACTIVATE_PROPERTIES(obj);
|
||||
|
||||
auto props = obj.GetProperties();
|
||||
std::cout << "Registered " << props.size() << " properties." << std::endl;
|
||||
|
||||
for (auto* p : props) {
|
||||
std::cout << "Prop: " << p->GetName()
|
||||
<< " Group: " << p->GetGroup()
|
||||
<< " Qualified: " << p->GetQualifiedName() << std::endl;
|
||||
}
|
||||
|
||||
// Check if nested properties are registered
|
||||
PropertyBase* p1 = obj.GetProperty("Position.x");
|
||||
PropertyBase* p2 = obj.GetProperty("Position.y");
|
||||
PropertyBase* p3 = obj.GetProperty("Orientation.x");
|
||||
PropertyBase* p4 = obj.GetProperty("Orientation.y");
|
||||
PropertyBase* p5 = obj.GetProperty("weight");
|
||||
|
||||
assert(p1 != nullptr && "Position.x not found");
|
||||
assert(p2 != nullptr && "Position.y not found");
|
||||
assert(p3 != nullptr && "Orientation.x not found");
|
||||
assert(p4 != nullptr && "Orientation.y not found");
|
||||
assert(p5 != nullptr && "weight not found");
|
||||
|
||||
assert(p1->GetGroup() == "Position");
|
||||
assert(p2->GetGroup() == "Position");
|
||||
assert(p3->GetGroup() == "Orientation");
|
||||
assert(p4->GetGroup() == "Orientation");
|
||||
assert(p5->GetGroup() == "");
|
||||
|
||||
assert(p1->GetQualifiedName() == "Position.x");
|
||||
assert(p5->GetQualifiedName() == "weight");
|
||||
|
||||
std::cout << "Property Grouping Tests PASSED!" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -12,6 +12,15 @@ ActionInitialization::ActionInitialization(EmitterPrimary *emitter, SimulationCo
|
||||
{}
|
||||
|
||||
ActionInitialization::~ActionInitialization() {}
|
||||
|
||||
// Lightweight wrapper to avoid double-free in Geant4 EventManager
|
||||
class SteppingActionWrapper : public G4UserSteppingAction {
|
||||
public:
|
||||
SteppingActionWrapper(SteppingAction *real) : m_Real(real) {}
|
||||
virtual void UserSteppingAction(const G4Step* step) override { m_Real->UserSteppingAction(step); }
|
||||
private:
|
||||
SteppingAction *m_Real;
|
||||
};
|
||||
|
||||
void ActionInitialization::BuildForMaster() const {}
|
||||
|
||||
@@ -21,10 +30,12 @@ void ActionInitialization::Build() const {
|
||||
} else {
|
||||
SetUserAction(new EmitterPrimary());
|
||||
}
|
||||
|
||||
|
||||
SteppingAction *sa = new SteppingAction(m_Context);
|
||||
SetUserAction(static_cast<G4UserSteppingAction*>(sa));
|
||||
// EventManager will delete sa via this slot
|
||||
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
||||
// EventManager will delete the wrapper, leaving sa alive to be deleted once.
|
||||
SetUserAction(new SteppingActionWrapper(sa));
|
||||
}
|
||||
|
||||
} // namespace Geant
|
||||
|
||||
@@ -17,6 +17,14 @@ DetectorActionInitialization::DetectorActionInitialization(EmitterPrimary *emitt
|
||||
{}
|
||||
|
||||
DetectorActionInitialization::~DetectorActionInitialization() {}
|
||||
|
||||
class DetectorSteppingActionWrapper : public G4UserSteppingAction {
|
||||
public:
|
||||
DetectorSteppingActionWrapper(DetectorSteppingAction *real) : m_Real(real) {}
|
||||
virtual void UserSteppingAction(const G4Step* step) override { m_Real->UserSteppingAction(step); }
|
||||
private:
|
||||
DetectorSteppingAction *m_Real;
|
||||
};
|
||||
|
||||
void DetectorActionInitialization::BuildForMaster() const {}
|
||||
|
||||
@@ -34,8 +42,10 @@ void DetectorActionInitialization::Build() const {
|
||||
if (m_Output) {
|
||||
DetectorSteppingAction *sa = new DetectorSteppingAction(m_Output, m_Planes);
|
||||
sa->SetVerbosity(m_Verbosity);
|
||||
SetUserAction(static_cast<G4UserSteppingAction*>(sa));
|
||||
// EventManager will delete sa via the Event slot
|
||||
SetUserAction(static_cast<G4UserEventAction*>(sa));
|
||||
// EventManager will delete the wrapper, leaving sa alive for the other deletion.
|
||||
SetUserAction(new DetectorSteppingActionWrapper(sa));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ static void CheckGeant4Environment() {
|
||||
|
||||
class SceneImpl {
|
||||
public:
|
||||
SceneImpl() : m_RunManager(G4RunManagerFactory::CreateRunManager(G4RunManagerType::Default)),
|
||||
SceneImpl() : m_RunManager(G4RunManagerFactory::CreateRunManager(G4RunManagerType::Serial)),
|
||||
m_Emitter(nullptr),
|
||||
m_InitCalled(false) {
|
||||
m_RunManager->SetUserInitialization(new PhysicsList);
|
||||
@@ -102,6 +102,7 @@ void Scene::AddSolid(Solid *solid, Solid *parent) {
|
||||
|
||||
const Solid* Scene::GetWorld() const { return d->m_World; }
|
||||
ContainerBox* Scene::GetWorldBox() const { return &d->m_WorldBox; }
|
||||
const Vector<Solid*>& Scene::GetSolids() const { return d->m_Solids; }
|
||||
|
||||
void Scene::ConstructWorldBox(const Vector3f &size, const char *material) {
|
||||
d->m_WorldBox.Scale(size);
|
||||
|
||||
@@ -58,6 +58,9 @@ public:
|
||||
|
||||
ContainerBox* GetWorldBox() const;
|
||||
|
||||
/// Get the list of solids in the scene
|
||||
const Vector<Solid*>& GetSolids() const;
|
||||
|
||||
/// Set the primary generator (emitter) for the simulation.
|
||||
/// The Scene does NOT take ownership of the emitter.
|
||||
void SetEmitter(EmitterPrimary *emitter);
|
||||
|
||||
@@ -151,6 +151,7 @@ TessellatedSolid::TessellatedSolid(const char *name)
|
||||
}
|
||||
|
||||
void TessellatedSolid::SetMesh(TriangleMesh &mesh) {
|
||||
this->m_Mesh = mesh;
|
||||
G4TessellatedSolid *ts = this->m_Solid;
|
||||
for (int i = 0; i < mesh.Triangles().size(); ++i) {
|
||||
const Vector3i &trg = mesh.Triangles().at(i);
|
||||
@@ -165,6 +166,9 @@ void TessellatedSolid::SetMesh(TriangleMesh &mesh) {
|
||||
}
|
||||
}
|
||||
|
||||
void TessellatedSolid::Update() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -93,11 +93,14 @@ public:
|
||||
void SetMesh(TriangleMesh &mesh);
|
||||
uLibGetMacro(Solid, G4TessellatedSolid *)
|
||||
virtual G4VSolid* GetG4Solid() const override { return (G4VSolid*)m_Solid; }
|
||||
|
||||
const TriangleMesh& GetMesh() const { return m_Mesh; }
|
||||
|
||||
public slots:
|
||||
void Update();
|
||||
|
||||
private :
|
||||
TriangleMesh m_Mesh;
|
||||
G4TessellatedSolid *m_Solid;
|
||||
};
|
||||
|
||||
|
||||
146
src/Math/Assembly.cpp
Normal file
146
src/Math/Assembly.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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 >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "Math/Assembly.h"
|
||||
#include "Math/ContainerBox.h"
|
||||
#include "Math/Cylinder.h"
|
||||
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace uLib {
|
||||
|
||||
Assembly::Assembly()
|
||||
: ObjectsContext(),
|
||||
AffineTransform(),
|
||||
m_BBoxMin(Vector3f::Zero()),
|
||||
m_BBoxMax(Vector3f::Zero()),
|
||||
m_ShowBoundingBox(false),
|
||||
m_GroupSelection(true) {}
|
||||
|
||||
Assembly::Assembly(const Assembly ©)
|
||||
: ObjectsContext(copy),
|
||||
AffineTransform(copy),
|
||||
m_BBoxMin(copy.m_BBoxMin),
|
||||
m_BBoxMax(copy.m_BBoxMax),
|
||||
m_ShowBoundingBox(copy.m_ShowBoundingBox),
|
||||
m_GroupSelection(copy.m_GroupSelection) {}
|
||||
|
||||
Assembly::~Assembly() {}
|
||||
|
||||
void Assembly::AddObject(Object *obj) {
|
||||
if (auto *at = dynamic_cast<AffineTransform *>(obj)) {
|
||||
at->SetParent(this);
|
||||
}
|
||||
ObjectsContext::AddObject(obj);
|
||||
}
|
||||
|
||||
void Assembly::RemoveObject(Object *obj) {
|
||||
if (auto *at = dynamic_cast<AffineTransform *>(obj)) {
|
||||
if (at->GetParent() == this)
|
||||
at->SetParent(nullptr);
|
||||
}
|
||||
ObjectsContext::RemoveObject(obj);
|
||||
}
|
||||
|
||||
void Assembly::ComputeBoundingBox() {
|
||||
const auto &objects = this->GetObjects();
|
||||
if (objects.empty()) {
|
||||
m_BBoxMin = Vector3f::Zero();
|
||||
m_BBoxMax = Vector3f::Zero();
|
||||
return;
|
||||
}
|
||||
|
||||
float inf = std::numeric_limits<float>::max();
|
||||
m_BBoxMin = Vector3f(inf, inf, inf);
|
||||
m_BBoxMax = Vector3f(-inf, -inf, -inf);
|
||||
|
||||
Matrix4f invAsm = this->GetWorldMatrix().inverse();
|
||||
|
||||
for (Object *obj : objects) {
|
||||
if (auto *box = dynamic_cast<ContainerBox *>(obj)) {
|
||||
// ContainerBox: wm is matrix from unit cube [0,1] to assembly base
|
||||
Matrix4f m = invAsm * box->GetWorldMatrix();
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
float x = (i & 1) ? 1.0f : 0.0f;
|
||||
float y = (i & 2) ? 1.0f : 0.0f;
|
||||
float z = (i & 4) ? 1.0f : 0.0f;
|
||||
Vector4f corner = m * Vector4f(x, y, z, 1.0f);
|
||||
for (int a = 0; a < 3; ++a) {
|
||||
m_BBoxMin(a) = std::min(m_BBoxMin(a), corner(a));
|
||||
m_BBoxMax(a) = std::max(m_BBoxMax(a), corner(a));
|
||||
}
|
||||
}
|
||||
} else if (auto *cyl = dynamic_cast<Cylinder *>(obj)) {
|
||||
// Cylinder: centered [-1, 1] radial, [-0.5, 0.5] height
|
||||
Matrix4f m = invAsm * cyl->GetWorldMatrix();
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
float x = (i & 1) ? 1.0f : -1.0f;
|
||||
float y = (i & 2) ? 0.5f : -0.5f;
|
||||
float z = (i & 4) ? 1.0f : -1.0f;
|
||||
Vector4f corner = m * Vector4f(x, y, z, 1.0f);
|
||||
for (int a = 0; a < 3; ++a) {
|
||||
m_BBoxMin(a) = std::min(m_BBoxMin(a), corner(a));
|
||||
m_BBoxMax(a) = std::max(m_BBoxMax(a), corner(a));
|
||||
}
|
||||
}
|
||||
} else if (auto *subAsm = dynamic_cast<Assembly *>(obj)) {
|
||||
// Recursive AABB for nested assemblies
|
||||
subAsm->ComputeBoundingBox();
|
||||
Vector3f subMin, subMax;
|
||||
subAsm->GetBoundingBox(subMin, subMax);
|
||||
Matrix4f m = invAsm * subAsm->GetWorldMatrix();
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
float x = (i & 1) ? subMax(0) : subMin(0);
|
||||
float y = (i & 2) ? subMax(1) : subMin(1);
|
||||
float z = (i & 4) ? subMax(2) : subMin(2);
|
||||
Vector4f corner = m * Vector4f(x, y, z, 1.0f);
|
||||
for (int a = 0; a < 3; ++a) {
|
||||
m_BBoxMin(a) = std::min(m_BBoxMin(a), corner(a));
|
||||
m_BBoxMax(a) = std::max(m_BBoxMax(a), corner(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Assembly::GetBoundingBox(Vector3f &bbMin, Vector3f &bbMax) const {
|
||||
bbMin = m_BBoxMin;
|
||||
bbMax = m_BBoxMax;
|
||||
}
|
||||
|
||||
ContainerBox Assembly::GetBoundingBoxAsContainer() const {
|
||||
ContainerBox bb;
|
||||
Vector3f size = m_BBoxMax - m_BBoxMin;
|
||||
bb.SetSize(size);
|
||||
bb.SetPosition(m_BBoxMin);
|
||||
return bb;
|
||||
}
|
||||
|
||||
void Assembly::SetShowBoundingBox(bool show) {
|
||||
m_ShowBoundingBox = show;
|
||||
this->Updated();
|
||||
}
|
||||
|
||||
bool Assembly::GetShowBoundingBox() const {
|
||||
return m_ShowBoundingBox;
|
||||
}
|
||||
|
||||
void Assembly::SetGroupSelection(bool group) {
|
||||
m_GroupSelection = group;
|
||||
}
|
||||
|
||||
bool Assembly::GetGroupSelection() const {
|
||||
return m_GroupSelection;
|
||||
}
|
||||
|
||||
} // namespace uLib
|
||||
109
src/Math/Assembly.h
Normal file
109
src/Math/Assembly.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef U_ASSEMBLY_H
|
||||
#define U_ASSEMBLY_H
|
||||
|
||||
#include "Core/ObjectsContext.h"
|
||||
#include "Math/ContainerBox.h"
|
||||
#include "Math/Transform.h"
|
||||
|
||||
namespace uLib {
|
||||
|
||||
/**
|
||||
* @brief Assembly groups geometric objects (ContainerBox, Cylinder, etc.)
|
||||
* under a common transformation.
|
||||
*
|
||||
* Assembly derives from ObjectsContext so objects can be added/removed
|
||||
* dynamically. It also inherits AffineTransform to provide a group-level
|
||||
* transformation that is applied on top of each child's own transform.
|
||||
*
|
||||
* A bounding box is automatically computed from all contained objects and
|
||||
* can be queried or shown/hidden through the VTK puppet.
|
||||
*/
|
||||
class Assembly : public ObjectsContext, public AffineTransform {
|
||||
public:
|
||||
virtual const char *GetClassName() const override { return "Assembly"; }
|
||||
|
||||
Assembly();
|
||||
Assembly(const Assembly ©);
|
||||
virtual ~Assembly();
|
||||
|
||||
virtual void AddObject(Object* obj) override;
|
||||
virtual void RemoveObject(Object* obj) override;
|
||||
|
||||
/**
|
||||
* @brief Recomputes the axis-aligned bounding box enclosing all children.
|
||||
* Stores the result internally.
|
||||
*/
|
||||
void ComputeBoundingBox();
|
||||
|
||||
/**
|
||||
* @brief Returns the bounding box as min/max corners (in assembly-local
|
||||
* coordinates).
|
||||
*/
|
||||
void GetBoundingBox(Vector3f &bbMin, Vector3f &bbMax) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the bounding box as a ContainerBox (positioned
|
||||
* at bbMin, sized bbMax-bbMin, parented to this transform).
|
||||
*/
|
||||
ContainerBox GetBoundingBoxAsContainer() const;
|
||||
|
||||
/**
|
||||
* @brief Controls whether the bounding box wireframe should be shown
|
||||
* in the viewer (used by the VTK puppet).
|
||||
*/
|
||||
void SetShowBoundingBox(bool show);
|
||||
bool GetShowBoundingBox() const;
|
||||
|
||||
/**
|
||||
* @brief Controls selection behavior.
|
||||
* If true (default), clicking any child within the assembly will select
|
||||
* the assembly itself. If false, individual children can be picked.
|
||||
*/
|
||||
void SetGroupSelection(bool group);
|
||||
bool GetGroupSelection() const;
|
||||
|
||||
signals:
|
||||
virtual void Updated() override {
|
||||
if (m_InUpdated) return; // break signal recursion
|
||||
m_InUpdated = true;
|
||||
this->ComputeBoundingBox();
|
||||
ULIB_SIGNAL_EMIT(Assembly::Updated);
|
||||
m_InUpdated = false;
|
||||
}
|
||||
|
||||
private:
|
||||
Vector3f m_BBoxMin;
|
||||
Vector3f m_BBoxMax;
|
||||
bool m_ShowBoundingBox;
|
||||
bool m_GroupSelection;
|
||||
bool m_InUpdated = false;
|
||||
};
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_ASSEMBLY_H
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
set(HEADERS ContainerBox.h
|
||||
Cylinder.h
|
||||
Assembly.h
|
||||
Dense.h
|
||||
Geometry.h
|
||||
Transform.h
|
||||
@@ -33,6 +34,7 @@ set(SOURCES VoxRaytracer.cpp
|
||||
VoxImage.cpp
|
||||
TriangleMesh.cpp
|
||||
QuadMesh.cpp
|
||||
Assembly.cpp
|
||||
Dense.cpp
|
||||
Structured2DGrid.cpp
|
||||
Structured4DGrid.cpp
|
||||
|
||||
@@ -85,10 +85,11 @@ public:
|
||||
* @param copy The ContainerBox instance to copy from.
|
||||
*/
|
||||
ContainerBox(const ContainerBox ©)
|
||||
: m_LocalT(this), // BaseClass is Parent of m_LocalTransform
|
||||
: m_LocalT(copy.m_LocalT), // Copy local transform state
|
||||
AffineTransform(copy),
|
||||
p_Size(this, "Size", copy.p_Size),
|
||||
p_Origin(this, "Origin", copy.p_Origin) {
|
||||
m_LocalT.SetParent(this); // Reset parent to the new object
|
||||
Object::connect(&p_Size, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncSize);
|
||||
Object::connect(&p_Origin, &Property<Vector3f>::PropertyChanged, this, &ContainerBox::SyncOrigin);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ public:
|
||||
Vector3f GetPoint(const Id_t id) const;
|
||||
|
||||
inline std::vector<Vector3f> & Points() { return this->m_Points; }
|
||||
inline const std::vector<Vector3f> & Points() const { return this->m_Points; }
|
||||
inline std::vector<Vector4i> & Quads() { return this->m_Quads; }
|
||||
inline const std::vector<Vector4i> & Quads() const { return this->m_Quads; }
|
||||
|
||||
const Vector4i & GetQuad(const Id_t id) const { return m_Quads.at(id); }
|
||||
Vector3f GetNormal(const Id_t id) const;
|
||||
|
||||
@@ -69,6 +69,8 @@ public:
|
||||
m_Parent(NULL)
|
||||
{}
|
||||
|
||||
virtual ~AffineTransform() {}
|
||||
|
||||
AffineTransform(AffineTransform *parent) :
|
||||
m_T(Matrix4f::Identity()),
|
||||
m_Parent(parent)
|
||||
|
||||
@@ -55,7 +55,9 @@ public:
|
||||
Vector3f GetPoint(const Id_t id) const;
|
||||
|
||||
inline std::vector<Vector3f> & Points() { return this->m_Points; }
|
||||
inline const std::vector<Vector3f> & Points() const { return this->m_Points; }
|
||||
inline std::vector<Vector3i> & Triangles() { return this->m_Triangles; }
|
||||
inline const std::vector<Vector3i> & Triangles() const { return this->m_Triangles; }
|
||||
|
||||
const Vector3i & GetTriangle(const Id_t id) const { return m_Triangles.at(id); }
|
||||
Vector3f GetNormal(const Id_t id) const;
|
||||
|
||||
@@ -109,8 +109,20 @@ public:
|
||||
|
||||
VoxImage(const Vector3i &size);
|
||||
|
||||
VoxImage(const VoxImage<T> ©) : BaseClass(copy) {
|
||||
this->m_Data = copy.m_Data;
|
||||
// Use compiler-generated copy constructor and assignment operator
|
||||
|
||||
VoxImage<T>& operator=(const VoxImage<T>& other) {
|
||||
if (this != &other) {
|
||||
// Copy the base class non-virtual parts (dims, spacing, position, etc.)
|
||||
// WITHOUT going through the virtual SetDims chain (which would call
|
||||
// m_Data.resize() THEN DataAllocator::operator= will resize again → double-free).
|
||||
// Instead, directly copy DataAllocator and update the StructuredGrid state.
|
||||
this->m_Data = other.m_Data;
|
||||
StructuredGrid::SetDims(other.GetDims());
|
||||
this->SetSpacing(other.GetSpacing());
|
||||
this->SetPosition(other.GetPosition());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline DataAllocator<T> &Data() { return this->m_Data; }
|
||||
|
||||
@@ -27,12 +27,16 @@
|
||||
#define VOXIMAGEFILTER_H
|
||||
|
||||
#include "Core/StaticInterface.h"
|
||||
#include "Core/Algorithm.h"
|
||||
#include "Math/Dense.h"
|
||||
|
||||
#include "Math/VoxImage.h"
|
||||
|
||||
namespace uLib {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Kernel shape interface (static check for operator()(float) and operator()(Vector3f))
|
||||
|
||||
namespace Interface {
|
||||
struct VoxImageFilterShape {
|
||||
template <class Self> void check_structural() {
|
||||
@@ -42,63 +46,95 @@ struct VoxImageFilterShape {
|
||||
};
|
||||
} // namespace Interface
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Forward declaration
|
||||
|
||||
template <typename VoxelT> class Kernel;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Abstract interface (type-erased, used by python bindings)
|
||||
|
||||
namespace Abstract {
|
||||
class VoxImageFilter {
|
||||
public:
|
||||
virtual void Run() = 0;
|
||||
|
||||
virtual void SetImage(Abstract::VoxImage *image) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~VoxImageFilter() {}
|
||||
};
|
||||
} // namespace Abstract
|
||||
|
||||
template <typename VoxelT, typename AlgorithmT>
|
||||
class VoxImageFilter : public Abstract::VoxImageFilter, public Object {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// VoxImageFilter — kernel-based voxel filter using CRTP + Algorithm
|
||||
//
|
||||
// Template parameters:
|
||||
// VoxelT — voxel data type (must satisfy Interface::Voxel)
|
||||
// CrtpImplT — concrete filter subclass (CRTP), must provide:
|
||||
// float Evaluate(const VoxImage<VoxelT>& buffer, int index)
|
||||
//
|
||||
// Inherits Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*> so that filters
|
||||
// can be used with AlgorithmTask for scheduled/async execution, and chained
|
||||
// via encoder/decoder.
|
||||
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
class VoxImageFilter : public Abstract::VoxImageFilter,
|
||||
public Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*> {
|
||||
public:
|
||||
|
||||
virtual const char * GetClassName() const { return "VoxImageFilter"; }
|
||||
virtual const char* GetClassName() const { return "VoxImageFilter"; }
|
||||
|
||||
VoxImageFilter(const Vector3i &size);
|
||||
|
||||
// Algorithm interface ////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Process implements Algorithm::Process.
|
||||
* Applies the filter in-place on the input image and returns it.
|
||||
*/
|
||||
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override;
|
||||
|
||||
/**
|
||||
* @brief Run implements Abstract::VoxImageFilter::Run.
|
||||
* Calls Process on the current image.
|
||||
*/
|
||||
void Run();
|
||||
|
||||
// Device awareness ///////////////////////////////////////////////////////////
|
||||
|
||||
/** @brief Returns VRAM if image or kernel data is on GPU, RAM otherwise. */
|
||||
MemoryDevice GetPreferredDevice() const override {
|
||||
if (m_Image && m_Image->Data().GetDevice() == MemoryDevice::VRAM)
|
||||
return MemoryDevice::VRAM;
|
||||
if (m_KernelData.ConstData().GetDevice() == MemoryDevice::VRAM)
|
||||
return MemoryDevice::VRAM;
|
||||
return MemoryDevice::RAM;
|
||||
}
|
||||
|
||||
// Kernel setup ///////////////////////////////////////////////////////////////
|
||||
|
||||
void SetKernelNumericXZY(const std::vector<float> &numeric);
|
||||
|
||||
void SetKernelSpherical(float (*shape)(float));
|
||||
|
||||
template <class ShapeT> void SetKernelSpherical(ShapeT shape);
|
||||
|
||||
void SetKernelWeightFunction(float (*shape)(const Vector3f &));
|
||||
|
||||
template <class ShapeT> void SetKernelWeightFunction(ShapeT shape);
|
||||
|
||||
inline const Kernel<VoxelT> &GetKernelData() const {
|
||||
return this->m_KernelData;
|
||||
}
|
||||
inline Kernel<VoxelT> &GetKernelData() { return this->m_KernelData; }
|
||||
// Accessors //////////////////////////////////////////////////////////////////
|
||||
|
||||
inline VoxImage<VoxelT> *GetImage() const { return this->m_Image; }
|
||||
const Kernel<VoxelT> &GetKernelData() const { return m_KernelData; }
|
||||
Kernel<VoxelT> &GetKernelData() { return m_KernelData; }
|
||||
|
||||
VoxImage<VoxelT> *GetImage() const { return m_Image; }
|
||||
void SetImage(Abstract::VoxImage *image);
|
||||
|
||||
protected:
|
||||
float Convolve(const VoxImage<VoxelT> &buffer, int index); // remove //
|
||||
|
||||
void SetKernelOffset();
|
||||
|
||||
float Distance2(const Vector3i &v);
|
||||
|
||||
// protected members for algorithm access //
|
||||
Kernel<VoxelT> m_KernelData;
|
||||
VoxImage<VoxelT> *m_Image;
|
||||
|
||||
private:
|
||||
AlgorithmT *t_Algoritm;
|
||||
CrtpImplT *m_CrtpImpl;
|
||||
};
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
|
||||
namespace uLib {
|
||||
|
||||
// KERNEL //////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// KERNEL ////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T> class Kernel : public StructuredData {
|
||||
typedef StructuredData BaseClass;
|
||||
@@ -41,13 +43,12 @@ template <typename T> class Kernel : public StructuredData {
|
||||
public:
|
||||
Kernel(const Vector3i &size);
|
||||
|
||||
inline T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
||||
inline T &operator[](const int &id) { return m_Data[id]; }
|
||||
inline int GetCenterData() const;
|
||||
T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
||||
T &operator[](const int &id) { return m_Data[id]; }
|
||||
int GetCenterData() const;
|
||||
|
||||
inline DataAllocator<T> &Data() { return this->m_Data; }
|
||||
|
||||
inline const DataAllocator<T> &ConstData() const { return this->m_Data; }
|
||||
DataAllocator<T> &Data() { return m_Data; }
|
||||
const DataAllocator<T> &ConstData() const { return m_Data; }
|
||||
|
||||
void PrintSelf(std::ostream &o) const;
|
||||
|
||||
@@ -60,12 +61,14 @@ Kernel<T>::Kernel(const Vector3i &size) : BaseClass(size), m_Data(size.prod()) {
|
||||
Interface::IsA<T, Interface::Voxel>();
|
||||
}
|
||||
|
||||
template <typename T> inline int Kernel<T>::GetCenterData() const {
|
||||
template <typename T>
|
||||
int Kernel<T>::GetCenterData() const {
|
||||
static int center = Map(this->GetDims() / 2);
|
||||
return center;
|
||||
}
|
||||
|
||||
template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
|
||||
template <typename T>
|
||||
void Kernel<T>::PrintSelf(std::ostream &o) const {
|
||||
o << " Filter Kernel Dump [XZ_Y]: \n";
|
||||
Vector3i index;
|
||||
o << "\n Value: \n\n"
|
||||
@@ -96,26 +99,42 @@ template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// VOXIMAGEFILTER IMPLEMENTATION /////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define _TPL_ template <typename VoxelT, typename AlgorithmT>
|
||||
#define _TPLT_ VoxelT, AlgorithmT
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
VoxImageFilter<VoxelT, CrtpImplT>::VoxImageFilter(const Vector3i &size)
|
||||
: m_KernelData(size)
|
||||
, m_Image(nullptr)
|
||||
, m_CrtpImpl(static_cast<CrtpImplT *>(this))
|
||||
{}
|
||||
|
||||
_TPL_
|
||||
VoxImageFilter<_TPLT_>::VoxImageFilter(const Vector3i &size)
|
||||
: m_KernelData(size), t_Algoritm(static_cast<AlgorithmT *>(this)) {}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::Run() {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
VoxImage<VoxelT>* VoxImageFilter<VoxelT, CrtpImplT>::Process(
|
||||
VoxImage<VoxelT>* const& image) {
|
||||
if (m_Image != image) SetImage(image);
|
||||
VoxImage<VoxelT> buffer = *m_Image;
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < m_Image->Data().size(); ++i)
|
||||
m_Image->operator[](i).Value = this->t_Algoritm->Evaluate(buffer, i);
|
||||
m_Image->operator[](i).Value = m_CrtpImpl->Evaluate(buffer, i);
|
||||
#pragma omp barrier
|
||||
return m_Image;
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::Run() {
|
||||
Process(m_Image);
|
||||
}
|
||||
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetImage(Abstract::VoxImage *image) {
|
||||
m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
|
||||
SetKernelOffset();
|
||||
}
|
||||
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelOffset() {
|
||||
Vector3i id(0, 0, 0);
|
||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||
@@ -127,10 +146,10 @@ void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_
|
||||
float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
float VoxImageFilter<VoxelT, CrtpImplT>::Distance2(const Vector3i &v) {
|
||||
Vector3i tmp = v;
|
||||
const Vector3i &dim = this->m_KernelData.GetDims();
|
||||
const Vector3i &dim = m_KernelData.GetDims();
|
||||
Vector3i center = dim / 2;
|
||||
tmp = tmp - center;
|
||||
center = center.cwiseProduct(center);
|
||||
@@ -140,12 +159,9 @@ float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
||||
0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2)));
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelNumericXZY(
|
||||
const std::vector<float> &numeric) {
|
||||
// set data order //
|
||||
StructuredData::Order order = m_KernelData.GetDataOrder();
|
||||
// m_KernelData.SetDataOrder(StructuredData::XZY);
|
||||
Vector3i id;
|
||||
int index = 0;
|
||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||
@@ -156,38 +172,39 @@ void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
||||
}
|
||||
}
|
||||
}
|
||||
// m_KernelData.SetDataOrder(order);
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(
|
||||
float (*shape)(float)) {
|
||||
Vector3i id;
|
||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||
id << x, y, z;
|
||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
||||
m_KernelData[id].Value = shape(Distance2(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_ template <class ShapeT>
|
||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
template <class ShapeT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(ShapeT shape) {
|
||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||
Vector3i id;
|
||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||
id << x, y, z;
|
||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
||||
m_KernelData[id].Value = shape(Distance2(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(
|
||||
float (*shape)(const Vector3f &)) {
|
||||
const Vector3i &dim = m_KernelData.GetDims();
|
||||
Vector3i id;
|
||||
@@ -195,20 +212,19 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
||||
for (int y = 0; y < dim(1); ++y) {
|
||||
for (int z = 0; z < dim(2); ++z) {
|
||||
for (int x = 0; x < dim(0); ++x) {
|
||||
// get voxels centroid coords from kernel center //
|
||||
id << x, y, z;
|
||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||
// compute function using given shape //
|
||||
m_KernelData[id].Value = shape(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_ template <class ShapeT>
|
||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
template <class ShapeT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(ShapeT shape) {
|
||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||
const Vector3i &dim = m_KernelData.GetDims();
|
||||
Vector3i id;
|
||||
@@ -216,45 +232,16 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
||||
for (int y = 0; y < dim(1); ++y) {
|
||||
for (int z = 0; z < dim(2); ++z) {
|
||||
for (int x = 0; x < dim(0); ++x) {
|
||||
// get voxels centroid coords from kernel center //
|
||||
id << x, y, z;
|
||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||
// compute function using given shape //
|
||||
m_KernelData[id].Value = shape(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetImage(Abstract::VoxImage *image) {
|
||||
this->m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
|
||||
this->SetKernelOffset();
|
||||
}
|
||||
|
||||
_TPL_
|
||||
float VoxImageFilter<_TPLT_>::Convolve(const VoxImage<VoxelT> &buffer,
|
||||
int index) {
|
||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
||||
const DataAllocator<VoxelT> &vker = m_KernelData.ConstData();
|
||||
int vox_size = vbuf.size();
|
||||
int ker_size = vker.size();
|
||||
int pos;
|
||||
float conv = 0, ksum = 0;
|
||||
for (int ik = 0; ik < ker_size; ++ik) {
|
||||
pos = index + vker[ik].Count - vker[m_KernelData.GetCenterData()].Count;
|
||||
pos = (pos + vox_size) % vox_size;
|
||||
conv += vbuf[pos].Value * vker[ik].Value;
|
||||
ksum += vker[ik].Value;
|
||||
}
|
||||
return conv / ksum;
|
||||
}
|
||||
|
||||
#undef _TPLT_
|
||||
#undef _TPL_
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
#endif // VOXIMAGEFILTER_HPP
|
||||
|
||||
@@ -109,7 +109,8 @@ public:
|
||||
}
|
||||
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
void Run() {
|
||||
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||
if (this->m_Image != image) this->SetImage(image);
|
||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||
|
||||
@@ -136,8 +137,9 @@ public:
|
||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
||||
mAtrim, mBtrim);
|
||||
cudaDeviceSynchronize();
|
||||
return this->m_Image;
|
||||
} else {
|
||||
BaseClass::Run();
|
||||
return BaseClass::Process(image);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -207,7 +209,8 @@ public:
|
||||
}
|
||||
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
void Run() {
|
||||
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||
if (this->m_Image != image) this->SetImage(image);
|
||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||
|
||||
@@ -234,8 +237,9 @@ public:
|
||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
||||
mAtrim, mBtrim);
|
||||
cudaDeviceSynchronize();
|
||||
return this->m_Image;
|
||||
} else {
|
||||
BaseClass::Run();
|
||||
return BaseClass::Process(image);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
#include "VoxImageFilter.h"
|
||||
#include <Math/Dense.h>
|
||||
|
||||
#define likely(expr) __builtin_expect(!!(expr), 1)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///// VOXIMAGE FILTER CUSTOM /////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -50,7 +48,7 @@ public:
|
||||
: BaseClass(size), m_CustomEvaluate(NULL) {}
|
||||
|
||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
||||
if (likely(m_CustomEvaluate)) {
|
||||
if (m_CustomEvaluate) {
|
||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
||||
const DataAllocator<VoxelT> &vker = this->m_KernelData.ConstData();
|
||||
int vox_size = vbuf.size();
|
||||
|
||||
@@ -67,7 +67,8 @@ public:
|
||||
VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {}
|
||||
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
void Run() {
|
||||
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||
if (this->m_Image != image) this->SetImage(image);
|
||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||
|
||||
@@ -92,8 +93,9 @@ public:
|
||||
LinearFilterKernel<<<blocksPerGrid, threadsPerBlock>>>(
|
||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count);
|
||||
cudaDeviceSynchronize();
|
||||
return this->m_Image;
|
||||
} else {
|
||||
BaseClass::Run();
|
||||
return BaseClass::Process(image);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
|
||||
#ifndef VOXIMAGEFILTERTHRESHOLD_HPP
|
||||
#define VOXIMAGEFILTERTHRESHOLD_HPP
|
||||
|
||||
@@ -39,40 +37,24 @@
|
||||
namespace uLib {
|
||||
|
||||
template <typename VoxelT>
|
||||
class VoxFilterAlgorithmThreshold :
|
||||
public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT> > {
|
||||
class VoxFilterAlgorithmThreshold
|
||||
: public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> {
|
||||
|
||||
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT> > BaseClass;
|
||||
// ULIB_OBJECT_PARAMETERS(BaseClass) {
|
||||
// float threshold;
|
||||
// };
|
||||
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> BaseClass;
|
||||
|
||||
float m_threshold;
|
||||
float m_threshold;
|
||||
|
||||
public:
|
||||
VoxFilterAlgorithmThreshold(const Vector3i &size) : BaseClass(size)
|
||||
{
|
||||
// init_parameters();
|
||||
m_threshold = 0;
|
||||
}
|
||||
VoxFilterAlgorithmThreshold(const Vector3i &size)
|
||||
: BaseClass(size), m_threshold(0) {}
|
||||
|
||||
inline void SetThreshold(float th) { m_threshold = th; }
|
||||
|
||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index)
|
||||
{
|
||||
return static_cast<float>(buffer.ConstData().at(index).Value >=
|
||||
// parameters().threshold);
|
||||
m_threshold );
|
||||
}
|
||||
void SetThreshold(float th) { m_threshold = th; }
|
||||
|
||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
||||
return static_cast<float>(buffer.ConstData().at(index).Value >= m_threshold);
|
||||
}
|
||||
};
|
||||
|
||||
//template <typename VoxelT>
|
||||
//inline void VoxFilterAlgorithmThreshold<VoxelT>::init_parameters()
|
||||
//{
|
||||
// parameters().threshold = 0;
|
||||
//}
|
||||
|
||||
}
|
||||
} // namespace uLib
|
||||
|
||||
#endif // VOXIMAGEFILTERTHRESHOLD_HPP
|
||||
|
||||
408
src/Math/testing/AlgorithmCudaChainTest.cpp
Normal file
408
src/Math/testing/AlgorithmCudaChainTest.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "testing-prototype.h"
|
||||
|
||||
#include "Core/Algorithm.h"
|
||||
#include "Math/VoxImage.h"
|
||||
#include "Math/VoxImageFilter.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
using namespace uLib;
|
||||
|
||||
struct TestVoxel {
|
||||
Scalarf Value;
|
||||
unsigned int Count;
|
||||
};
|
||||
|
||||
int main() {
|
||||
BEGIN_TESTING(AlgorithmCudaChain);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 1: Single filter — GetPreferredDevice reflects data location
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 1: GetPreferredDevice reflects data location ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||
image[Vector3i(5, 5, 5)].Value = 1;
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights(27, 1.0f);
|
||||
filter.SetImage(&image);
|
||||
filter.SetKernelNumericXZY(weights);
|
||||
|
||||
// Before VRAM move: should prefer RAM
|
||||
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||
TEST1(!filter.IsGPU());
|
||||
std::cout << " RAM mode: PreferredDevice=RAM, IsGPU=false OK\n";
|
||||
|
||||
// Move image data to VRAM
|
||||
image.Data().MoveToVRAM();
|
||||
|
||||
// After VRAM move: should prefer VRAM
|
||||
TEST1(filter.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||
TEST1(filter.IsGPU());
|
||||
std::cout << " VRAM mode: PreferredDevice=VRAM, IsGPU=true OK\n";
|
||||
|
||||
// Move back to RAM
|
||||
image.Data().MoveToRAM();
|
||||
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||
std::cout << " Back to RAM: PreferredDevice=RAM OK\n";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 2: Kernel data on VRAM also triggers GPU preference
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 2: Kernel on VRAM triggers GPU preference ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights(27, 1.0f);
|
||||
filter.SetImage(&image);
|
||||
filter.SetKernelNumericXZY(weights);
|
||||
|
||||
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||
|
||||
// Only kernel on VRAM
|
||||
filter.GetKernelData().Data().MoveToVRAM();
|
||||
TEST1(filter.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||
std::cout << " Kernel on VRAM: PreferredDevice=VRAM OK\n";
|
||||
|
||||
filter.GetKernelData().Data().MoveToRAM();
|
||||
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 3: Algorithm interface — Process through base pointer
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 3: Process through Algorithm base pointer ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||
image[Vector3i(5, 5, 5)].Value = 10;
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights(27, 1.0f);
|
||||
filter.SetImage(&image);
|
||||
filter.SetKernelNumericXZY(weights);
|
||||
|
||||
// Use through Algorithm base class pointer
|
||||
Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>* alg = &filter;
|
||||
|
||||
VoxImage<TestVoxel>* result = alg->Process(&image);
|
||||
TEST1(result == &image);
|
||||
std::cout << " Process through base pointer returned correct image OK\n";
|
||||
|
||||
// Verify filter actually ran (center voxel should be averaged)
|
||||
// With uniform 3x3x3 kernel and single non-zero voxel at center,
|
||||
// the center value should be 10/27 ≈ 0.37
|
||||
TEST1(image[Vector3i(5, 5, 5)].Value < 10.0f);
|
||||
std::cout << " Filter modified voxel values OK\n";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 4: Encoder/decoder chain — two filters linked
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 4: Encoder/decoder chain ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||
image[Vector3i(5, 5, 5)].Value = 100;
|
||||
|
||||
// First filter: linear smoothing
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filter1(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights1(27, 1.0f);
|
||||
filter1.SetImage(&image);
|
||||
filter1.SetKernelNumericXZY(weights1);
|
||||
|
||||
// Second filter: threshold
|
||||
VoxFilterAlgorithmThreshold<TestVoxel> filter2(Vector3i(1, 1, 1));
|
||||
filter2.SetThreshold(0.5f);
|
||||
filter2.SetImage(&image);
|
||||
// 1x1x1 kernel with value 1
|
||||
std::vector<float> weights2(1, 1.0f);
|
||||
filter2.SetKernelNumericXZY(weights2);
|
||||
|
||||
// Chain: filter1 → filter2
|
||||
filter1.SetDecoder(&filter2);
|
||||
filter2.SetEncoder(&filter1);
|
||||
|
||||
TEST1(filter1.GetDecoder() == &filter2);
|
||||
TEST1(filter2.GetEncoder() == &filter1);
|
||||
std::cout << " Chain linked: filter1 -> filter2 OK\n";
|
||||
|
||||
// Execute chain manually (encoder first, then decoder)
|
||||
filter1.Process(&image);
|
||||
float smoothed_center = image[Vector3i(5, 5, 5)].Value;
|
||||
std::cout << " After linear: center = " << smoothed_center << "\n";
|
||||
|
||||
filter2.Process(&image);
|
||||
float thresholded_center = image[Vector3i(5, 5, 5)].Value;
|
||||
std::cout << " After threshold: center = " << thresholded_center << "\n";
|
||||
|
||||
// After threshold, values should be 0 or 1
|
||||
TEST1(thresholded_center == 0.0f || thresholded_center == 1.0f);
|
||||
std::cout << " Chain execution produced valid results OK\n";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 5: CUDA chain — VRAM data through chained filters
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 5: VRAM data through chained filters ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||
image[Vector3i(5, 5, 5)].Value = 50;
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filter1(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights1(27, 1.0f);
|
||||
filter1.SetImage(&image);
|
||||
filter1.SetKernelNumericXZY(weights1);
|
||||
|
||||
VoxFilterAlgorithmAbtrim<TestVoxel> filter2(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights2(27, 1.0f);
|
||||
filter2.SetImage(&image);
|
||||
filter2.SetKernelNumericXZY(weights2);
|
||||
filter2.SetABTrim(1, 1);
|
||||
|
||||
// Chain
|
||||
filter1.SetDecoder(&filter2);
|
||||
filter2.SetEncoder(&filter1);
|
||||
|
||||
// Move data to VRAM
|
||||
image.Data().MoveToVRAM();
|
||||
filter1.GetKernelData().Data().MoveToVRAM();
|
||||
filter2.GetKernelData().Data().MoveToVRAM();
|
||||
|
||||
// Both filters should report VRAM preference
|
||||
TEST1(filter1.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||
TEST1(filter2.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||
TEST1(filter1.IsGPU());
|
||||
TEST1(filter2.IsGPU());
|
||||
std::cout << " Both filters detect VRAM preference OK\n";
|
||||
|
||||
// Verify the chain's device consistency
|
||||
auto* encoder = filter2.GetEncoder();
|
||||
TEST1(encoder != nullptr);
|
||||
TEST1(encoder->IsGPU());
|
||||
std::cout << " Encoder in chain also reports GPU OK\n";
|
||||
|
||||
#ifdef USE_CUDA
|
||||
// With CUDA: filters execute on GPU via Process()
|
||||
image.Data().MoveToRAM(); // reset for clean test
|
||||
image[Vector3i(5, 5, 5)].Value = 50;
|
||||
image.Data().MoveToVRAM();
|
||||
|
||||
filter1.Process(&image);
|
||||
TEST1(image.Data().GetDevice() == MemoryDevice::VRAM);
|
||||
std::cout << " CUDA: data stays in VRAM after filter1 OK\n";
|
||||
|
||||
filter2.Process(&image);
|
||||
TEST1(image.Data().GetDevice() == MemoryDevice::VRAM);
|
||||
std::cout << " CUDA: data stays in VRAM after filter2 OK\n";
|
||||
#else
|
||||
// Without CUDA: verify Process still works via CPU fallback
|
||||
image.Data().MoveToRAM();
|
||||
image[Vector3i(5, 5, 5)].Value = 50;
|
||||
|
||||
filter1.GetKernelData().Data().MoveToRAM();
|
||||
filter2.GetKernelData().Data().MoveToRAM();
|
||||
|
||||
filter1.Process(&image);
|
||||
filter2.Process(&image);
|
||||
std::cout << " No CUDA: CPU fallback executed correctly OK\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 6: AlgorithmTask with VRAM-aware filter
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 6: AlgorithmTask with VRAM-aware filter ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||
image[Vector3i(4, 4, 4)].Value = 20;
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights(27, 1.0f);
|
||||
filter.SetImage(&image);
|
||||
filter.SetKernelNumericXZY(weights);
|
||||
|
||||
// Set up task
|
||||
AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*> task;
|
||||
task.SetAlgorithm(&filter);
|
||||
task.SetMode(AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>::Cyclic);
|
||||
task.SetCycleTime(50);
|
||||
|
||||
// Run task for a few cycles
|
||||
task.Run(&image);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
task.Stop();
|
||||
|
||||
// After cyclic execution, the filter should have smoothed values
|
||||
TEST1(image[Vector3i(4, 4, 4)].Value < 20.0f);
|
||||
std::cout << " Task cyclic execution modified image OK\n";
|
||||
std::cout << " Center value after smoothing: "
|
||||
<< image[Vector3i(4, 4, 4)].Value << "\n";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 7: AlgorithmTask async with chained filters
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 7: AlgorithmTask async with filter ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||
image[Vector3i(4, 4, 4)].Value = 30;
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||
std::vector<float> weights(27, 1.0f);
|
||||
filter.SetImage(&image);
|
||||
filter.SetKernelNumericXZY(weights);
|
||||
|
||||
AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*> task;
|
||||
task.SetAlgorithm(&filter);
|
||||
task.SetMode(AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>::Async);
|
||||
|
||||
float before = image[Vector3i(4, 4, 4)].Value;
|
||||
|
||||
task.Run(&image);
|
||||
|
||||
// Trigger one execution
|
||||
task.Notify();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
task.Stop();
|
||||
|
||||
float after = image[Vector3i(4, 4, 4)].Value;
|
||||
TEST1(after < before);
|
||||
std::cout << " Async trigger: value " << before << " -> " << after << " OK\n";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 8: Device preference propagation in chain
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 8: Device preference propagation check ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||
image[Vector3i(4, 4, 4)].Value = 10;
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filterA(Vector3i(3, 3, 3));
|
||||
VoxFilterAlgorithmAbtrim<TestVoxel> filterB(Vector3i(3, 3, 3));
|
||||
VoxFilterAlgorithmThreshold<TestVoxel> filterC(Vector3i(1, 1, 1));
|
||||
|
||||
std::vector<float> w27(27, 1.0f);
|
||||
std::vector<float> w1(1, 1.0f);
|
||||
|
||||
filterA.SetImage(&image);
|
||||
filterA.SetKernelNumericXZY(w27);
|
||||
filterB.SetImage(&image);
|
||||
filterB.SetKernelNumericXZY(w27);
|
||||
filterB.SetABTrim(1, 1);
|
||||
filterC.SetImage(&image);
|
||||
filterC.SetKernelNumericXZY(w1);
|
||||
filterC.SetThreshold(0.1f);
|
||||
|
||||
// Chain: A → B → C
|
||||
filterA.SetDecoder(&filterB);
|
||||
filterB.SetEncoder(&filterA);
|
||||
filterB.SetDecoder(&filterC);
|
||||
filterC.SetEncoder(&filterB);
|
||||
|
||||
// All on RAM
|
||||
TEST1(!filterA.IsGPU());
|
||||
TEST1(!filterB.IsGPU());
|
||||
TEST1(!filterC.IsGPU());
|
||||
std::cout << " All filters on RAM OK\n";
|
||||
|
||||
// Move image to VRAM — filters A and B should detect it
|
||||
image.Data().MoveToVRAM();
|
||||
TEST1(filterA.IsGPU());
|
||||
TEST1(filterB.IsGPU());
|
||||
// filterC with 1x1x1 kernel doesn't have CUDA override, but still detects VRAM
|
||||
TEST1(filterC.IsGPU());
|
||||
std::cout << " Image on VRAM: all filters report GPU OK\n";
|
||||
|
||||
// Can walk the chain and check device consistency
|
||||
auto* step = static_cast<Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>*>(&filterA);
|
||||
bool all_gpu = true;
|
||||
while (step) {
|
||||
if (!step->IsGPU()) all_gpu = false;
|
||||
step = static_cast<Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>*>(step->GetDecoder());
|
||||
}
|
||||
TEST1(all_gpu);
|
||||
std::cout << " Chain walk: all steps report GPU OK\n";
|
||||
|
||||
image.Data().MoveToRAM();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TEST 9: Process through chain with Algorithm interface
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
std::cout << "\n--- Test 9: Sequential chain processing via Algorithm interface ---\n";
|
||||
|
||||
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||
// Set a pattern: single bright voxel
|
||||
image[Vector3i(5, 5, 5)].Value = 100;
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filterA(Vector3i(3, 3, 3));
|
||||
std::vector<float> w(27, 1.0f);
|
||||
filterA.SetImage(&image);
|
||||
filterA.SetKernelNumericXZY(w);
|
||||
|
||||
VoxFilterAlgorithmLinear<TestVoxel> filterB(Vector3i(3, 3, 3));
|
||||
filterB.SetImage(&image);
|
||||
filterB.SetKernelNumericXZY(w);
|
||||
|
||||
// Chain
|
||||
filterA.SetDecoder(&filterB);
|
||||
filterB.SetEncoder(&filterA);
|
||||
|
||||
// Process chain through base pointer
|
||||
using AlgType = Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>;
|
||||
AlgType* chain = &filterA;
|
||||
|
||||
// Walk and process
|
||||
AlgType* current = chain;
|
||||
while (current) {
|
||||
current->Process(&image);
|
||||
current = static_cast<AlgType*>(current->GetDecoder());
|
||||
}
|
||||
|
||||
// After two rounds of smoothing, the peak should be smaller than original
|
||||
float final_val = image[Vector3i(5, 5, 5)].Value;
|
||||
TEST1(final_val < 100.0f);
|
||||
std::cout << " Two-stage smoothing: peak = " << final_val << " OK\n";
|
||||
}
|
||||
|
||||
END_TESTING;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ set(TESTS
|
||||
QuadMeshTest
|
||||
BitCodeTest
|
||||
UnitsTest
|
||||
AlgorithmCudaChainTest
|
||||
)
|
||||
|
||||
set(LIBRARIES
|
||||
@@ -28,6 +29,6 @@ set(LIBRARIES
|
||||
uLib_add_tests(Math)
|
||||
|
||||
if(USE_CUDA)
|
||||
set_source_files_properties(VoxImageTest.cpp VoxImageCopyTest.cpp VoxImageFilterTest.cpp VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_source_files_properties(VoxImageTest.cpp VoxImageCopyTest.cpp VoxImageFilterTest.cpp VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp AlgorithmCudaChainTest.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_source_files_properties(VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES CXX_STANDARD 17 CUDA_STANDARD 17)
|
||||
endif()
|
||||
|
||||
@@ -52,7 +52,7 @@ int main()
|
||||
|
||||
// Test 1: Basic identity transformation and cylinder parameters
|
||||
{
|
||||
Cylinder cyl(2.0, 10.0);
|
||||
Cylinder cyl(2.0, 10.0, 2);
|
||||
std::cout << "Cyl R=" << cyl.GetRadius() << " H=" << cyl.GetHeight() << std::endl;
|
||||
std::cout << "Cyl World Matrix:\n" << cyl.GetWorldMatrix() << std::endl;
|
||||
|
||||
@@ -82,7 +82,7 @@ int main()
|
||||
|
||||
// Test 2: Translation
|
||||
{
|
||||
Cylinder cyl(1.0, 2.0);
|
||||
Cylinder cyl(1.0, 2.0, 2);
|
||||
cyl.SetPosition(Vector3f(10, 20, 30));
|
||||
|
||||
// Local base origin (0, 0, 0) -> World (10, 20, 30)
|
||||
@@ -96,7 +96,7 @@ int main()
|
||||
|
||||
// Test 3: Rotation and complex mapping
|
||||
{
|
||||
Cylinder cyl(5.0, 20.0);
|
||||
Cylinder cyl(5.0, 20.0, 2);
|
||||
cyl.SetPosition(Vector3f(1.0, 2.0, 3.0));
|
||||
// Rotate 90 degrees around X: Local Y becomes World Z, Local Z becomes World -Y
|
||||
cyl.Rotate(M_PI/2.0, Vector3f(1, 0, 0));
|
||||
|
||||
@@ -432,9 +432,9 @@ void init_math(py::module_ &m) {
|
||||
.def("AddPoint", &TriangleMesh::AddPoint)
|
||||
.def("AddTriangle",
|
||||
py::overload_cast<const Vector3i &>(&TriangleMesh::AddTriangle))
|
||||
.def("Points", &TriangleMesh::Points,
|
||||
.def("Points", py::overload_cast<>(&TriangleMesh::Points),
|
||||
py::return_value_policy::reference_internal)
|
||||
.def("Triangles", &TriangleMesh::Triangles,
|
||||
.def("Triangles", py::overload_cast<>(&TriangleMesh::Triangles),
|
||||
py::return_value_policy::reference_internal)
|
||||
.def("GetTriangle", &TriangleMesh::GetTriangle)
|
||||
.def("GetNormal", &TriangleMesh::GetNormal);
|
||||
@@ -444,9 +444,9 @@ void init_math(py::module_ &m) {
|
||||
.def("AddPoint", &QuadMesh::AddPoint)
|
||||
.def("AddQuad",
|
||||
py::overload_cast<const Vector4i &>(&QuadMesh::AddQuad))
|
||||
.def("Points", &QuadMesh::Points,
|
||||
.def("Points", py::overload_cast<>(&QuadMesh::Points),
|
||||
py::return_value_policy::reference_internal)
|
||||
.def("Quads", &QuadMesh::Quads,
|
||||
.def("Quads", py::overload_cast<>(&QuadMesh::Quads),
|
||||
py::return_value_policy::reference_internal)
|
||||
.def("GetQuad", &QuadMesh::GetQuad)
|
||||
.def("GetNormal", &QuadMesh::GetNormal);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "QCanvas.h"
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +41,13 @@ QCanvas::QCanvas(QWidget *parent) : QWidget(parent), m_canvas(nullptr) {
|
||||
if (!gApplication) {
|
||||
static int argc = 1;
|
||||
static char* argv[] = {(char*)"App", nullptr};
|
||||
new TApplication("App", &argc, argv);
|
||||
if (std::getenv("CTEST_PROJECT_NAME")) {
|
||||
static int bargc = 2;
|
||||
static char* bargv[] = {(char*)"App", (char*)"-b", nullptr};
|
||||
new TApplication("App", &bargc, bargv);
|
||||
} else {
|
||||
new TApplication("App", &argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the TCanvas associated with this QWidget
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <TCanvas.h>
|
||||
#include <TRandom.h>
|
||||
#include <TEnv.h>
|
||||
#include <cstdlib>
|
||||
#include "Root/QCanvas.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
@@ -37,10 +38,11 @@ int main(int argc, char **argv) {
|
||||
} else {
|
||||
std::cerr << "FAIL: Canvas is still NULL after show() and processEvents()" << std::endl;
|
||||
}
|
||||
|
||||
if (std::getenv("CTEST_PROJECT_NAME")) {
|
||||
return 0;
|
||||
}
|
||||
return app.exec();
|
||||
}
|
||||
#else
|
||||
int main() { return 0; }
|
||||
#endif
|
||||
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
|
||||
set(HEP_GEANT_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantEvent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantSolid.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkBoxSolid.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkTessellatedSolid.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantScene.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkEmitterPrimary.cpp
|
||||
PARENT_SCOPE)
|
||||
|
||||
set(HEP_GEANT_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantSolid.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkBoxSolid.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkTessellatedSolid.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkGeantScene.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkEmitterPrimary.h
|
||||
PARENT_SCOPE)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# TESTS
|
||||
set(TESTS
|
||||
vtkGeantEventTest
|
||||
vtkGeantSceneTest
|
||||
vtkSolidsTest
|
||||
vtkEmitterPrimaryTest
|
||||
vtkSkyPlaneEmitterPrimaryTest
|
||||
vtkCylinderEmitterPrimaryTest
|
||||
|
||||
65
src/Vtk/HEP/Geant/testing/vtkGeantSceneTest.cpp
Normal file
65
src/Vtk/HEP/Geant/testing/vtkGeantSceneTest.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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 >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "Geant/Solid.h"
|
||||
#include "HEP/Geant/Scene.h"
|
||||
#include "Math/ContainerBox.h"
|
||||
#include "Math/Dense.h"
|
||||
#include "Math/Units.h"
|
||||
#include "Vtk/uLibVtkViewer.h"
|
||||
#include "Vtk/HEP/Geant/vtkGeantScene.h"
|
||||
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace uLib;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
bool interactive = (argc > 1 && std::string(argv[1]) == "-i");
|
||||
|
||||
// 1. Setup Geant4 Scene
|
||||
Geant::Scene scene;
|
||||
scene.ConstructWorldBox(Vector3f(30_m, 30_m, 30_m), "G4_AIR");
|
||||
|
||||
// Add an iron cube inside the world
|
||||
ContainerBox iron_box;
|
||||
iron_box.Scale(Vector3f(10_m, 10_m, 10_m));
|
||||
iron_box.SetPosition(Vector3f(-5_m, -5_m, -5_m));
|
||||
Geant::BoxSolid* iron_cube = new Geant::BoxSolid("IronCube", &iron_box);
|
||||
iron_cube->SetNistMaterial("G4_Fe");
|
||||
iron_cube->Update();
|
||||
scene.AddSolid(iron_cube);
|
||||
scene.Initialize();
|
||||
|
||||
// 2. Build VTK scene representation
|
||||
Vtk::Viewer viewer;
|
||||
viewer.GetRenderer()->SetBackground(0.05, 0.05, 0.1);
|
||||
|
||||
Vtk::vtkGeantScene vtkScene(&scene);
|
||||
vtkScene.AddToViewer(viewer);
|
||||
|
||||
std::cout << "==================================================" << std::endl;
|
||||
std::cout << " vtkGeantScene Test" << std::endl;
|
||||
std::cout << " World box + 1 iron cube displayed" << std::endl;
|
||||
std::cout << "==================================================" << std::endl;
|
||||
|
||||
if (interactive) {
|
||||
viewer.ZoomAuto();
|
||||
viewer.Start();
|
||||
} else {
|
||||
std::cout << "Non-interactive test: scene initialized successfully" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
84
src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp
Normal file
84
src/Vtk/HEP/Geant/testing/vtkSolidsTest.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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 >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "Vtk/HEP/Geant/vtkBoxSolid.h"
|
||||
#include "Vtk/HEP/Geant/vtkTessellatedSolid.h"
|
||||
#include "Vtk/uLibVtkViewer.h"
|
||||
#include "HEP/Geant/Solid.h"
|
||||
#include "Math/ContainerBox.h"
|
||||
#include "Math/TriangleMesh.h"
|
||||
#include "Math/Units.h"
|
||||
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkActor.h>
|
||||
|
||||
using namespace uLib;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
bool interactive = (argc > 1 && std::string(argv[1]) == "-i");
|
||||
|
||||
// 1. Create a BoxSolid
|
||||
ContainerBox box;
|
||||
box.Scale(Vector3f(1_m, 2_m, 3_m));
|
||||
Geant::BoxSolid gBox("MyBox", &box);
|
||||
gBox.Update();
|
||||
|
||||
Vtk::vtkBoxSolid vtkBox(&gBox);
|
||||
|
||||
// 2. Create a TessellatedSolid
|
||||
Geant::TessellatedSolid gTess("MyTess");
|
||||
TriangleMesh mesh;
|
||||
// Create a simple pyramid
|
||||
mesh.Points().push_back(Vector3f(0, 0, 1_m)); // apex
|
||||
mesh.Points().push_back(Vector3f(-1_m, -1_m, 0));
|
||||
mesh.Points().push_back(Vector3f( 1_m, -1_m, 0));
|
||||
mesh.Points().push_back(Vector3f( 1_m, 1_m, 0));
|
||||
mesh.Points().push_back(Vector3f(-1_m, 1_m, 0));
|
||||
|
||||
mesh.Triangles().push_back(Vector3i(1, 2, 0));
|
||||
mesh.Triangles().push_back(Vector3i(2, 3, 0));
|
||||
mesh.Triangles().push_back(Vector3i(3, 4, 0));
|
||||
mesh.Triangles().push_back(Vector3i(4, 1, 0));
|
||||
mesh.Triangles().push_back(Vector3i(1, 3, 2)); // base
|
||||
mesh.Triangles().push_back(Vector3i(1, 4, 3)); // base
|
||||
|
||||
gTess.SetMesh(mesh);
|
||||
gTess.Update();
|
||||
|
||||
Vtk::vtkTessellatedSolid vtkTess(&gTess);
|
||||
|
||||
// 3. Visualization setup
|
||||
Vtk::Viewer viewer;
|
||||
vtkBox.AddToViewer(viewer);
|
||||
vtkTess.AddToViewer(viewer);
|
||||
|
||||
// Color them differently
|
||||
vtkActor::SafeDownCast(vtkBox.GetProp())->GetProperty()->SetColor(0.8, 0.2, 0.2); // Redish box
|
||||
vtkActor::SafeDownCast(vtkTess.GetProp())->GetProperty()->SetColor(0.2, 0.8, 0.2); // Greenish tess
|
||||
|
||||
// Position tessellated solid away from box
|
||||
Matrix4f trans = Matrix4f::Identity();
|
||||
trans.block<3,1>(0,3) = Vector3f(5_m, 0, 0);
|
||||
gTess.SetTransform(trans);
|
||||
vtkTess.Update();
|
||||
|
||||
std::cout << "..:: Testing vtkSolidsTest ::.." << std::endl;
|
||||
std::cout << "Box and Tessellated solids initialized." << std::endl;
|
||||
|
||||
if (interactive) {
|
||||
viewer.ZoomAuto();
|
||||
viewer.Start();
|
||||
} else {
|
||||
std::cout << "Non-interactive test passed." << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
57
src/Vtk/HEP/Geant/vtkBoxSolid.cpp
Normal file
57
src/Vtk/HEP/Geant/vtkBoxSolid.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Copyright (c) 2014, Universita' degli Studi Padova, INFN sez. di Padova
|
||||
All rights reserved
|
||||
|
||||
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "vtkBoxSolid.h"
|
||||
#include <vtkCubeSource.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkActor.h>
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
vtkBoxSolid::vtkBoxSolid(Geant::BoxSolid *content)
|
||||
: vtkGeantSolid(content), m_BoxContent(content) {
|
||||
// Re-run Update for box-specific pipe
|
||||
this->Update();
|
||||
}
|
||||
|
||||
vtkBoxSolid::~vtkBoxSolid() {}
|
||||
|
||||
void vtkBoxSolid::Update() {
|
||||
this->UpdateGeometry();
|
||||
this->UpdateTransform();
|
||||
}
|
||||
|
||||
void vtkBoxSolid::UpdateGeometry() {
|
||||
if (!m_BoxContent || !m_BoxContent->GetObject()) {
|
||||
// Fallback to base tessellation if no model object
|
||||
vtkGeantSolid::UpdateGeometry();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the underlying ContainerBox for precise geometry
|
||||
Vector3f size = m_BoxContent->GetObject()->GetSize();
|
||||
|
||||
vtkNew<vtkCubeSource> cube;
|
||||
cube->SetXLength(size(0));
|
||||
cube->SetYLength(size(1));
|
||||
cube->SetZLength(size(2));
|
||||
cube->Update();
|
||||
|
||||
vtkPolyData *poly = GetPolyData();
|
||||
if (poly) {
|
||||
poly->ShallowCopy(cube->GetOutput());
|
||||
poly->Modified();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
52
src/Vtk/HEP/Geant/vtkBoxSolid.h
Normal file
52
src/Vtk/HEP/Geant/vtkBoxSolid.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef U_VTKBOXSOLID_H
|
||||
#define U_VTKBOXSOLID_H
|
||||
|
||||
#include "vtkGeantSolid.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
/**
|
||||
* @brief VTK Puppet for visualizing a Geant::BoxSolid.
|
||||
*/
|
||||
class vtkBoxSolid : public vtkGeantSolid {
|
||||
public:
|
||||
vtkBoxSolid(Geant::BoxSolid *content);
|
||||
virtual ~vtkBoxSolid();
|
||||
|
||||
virtual void Update() override;
|
||||
virtual void UpdateGeometry() override;
|
||||
|
||||
protected:
|
||||
Geant::BoxSolid *m_BoxContent;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_VTKBOXSOLID_H
|
||||
102
src/Vtk/HEP/Geant/vtkGeantScene.cpp
Normal file
102
src/Vtk/HEP/Geant/vtkGeantScene.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "vtkGeantScene.h"
|
||||
#include "vtkGeantSolid.h"
|
||||
#include "vtkBoxSolid.h"
|
||||
#include "vtkTessellatedSolid.h"
|
||||
#include "Vtk/vtkViewport.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
vtkGeantScene::vtkGeantScene(Geant::Scene *scene)
|
||||
: m_Scene(scene), m_WorldPuppet(nullptr) {
|
||||
if (!m_Scene)
|
||||
return;
|
||||
|
||||
// 1. Create the world box wireframe puppet
|
||||
ContainerBox *worldBox = m_Scene->GetWorldBox();
|
||||
if (worldBox) {
|
||||
m_WorldPuppet = new vtkContainerBox(worldBox);
|
||||
m_WorldPuppet->SetRepresentation(Puppet::Wireframe);
|
||||
m_WorldPuppet->ShowScaleMeasures(true);
|
||||
}
|
||||
|
||||
// 2. Create puppets for each non-world solid
|
||||
const Geant::Solid *world = m_Scene->GetWorld();
|
||||
const Vector<Geant::Solid *> &solids = m_Scene->GetSolids();
|
||||
|
||||
for (Geant::Solid *solid : solids) {
|
||||
// Skip the world volume itself — it's already shown as the wireframe box
|
||||
if (solid == world)
|
||||
continue;
|
||||
|
||||
// Only create a puppet if the solid has a valid G4VSolid
|
||||
if (solid->GetG4Solid()) {
|
||||
vtkGeantSolid *vtkSolid = nullptr;
|
||||
if (auto *box = dynamic_cast<Geant::BoxSolid *>(solid)) {
|
||||
vtkSolid = new vtkBoxSolid(box);
|
||||
} else if (auto *tess = dynamic_cast<Geant::TessellatedSolid *>(solid)) {
|
||||
vtkSolid = new vtkTessellatedSolid(tess);
|
||||
} else {
|
||||
vtkSolid = new vtkGeantSolid(solid);
|
||||
vtkSolid->Update();
|
||||
}
|
||||
|
||||
if (vtkSolid) {
|
||||
m_SolidPuppets.push_back(vtkSolid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vtkGeantScene::~vtkGeantScene() {
|
||||
delete m_WorldPuppet;
|
||||
for (auto *p : m_SolidPuppets) {
|
||||
delete p;
|
||||
}
|
||||
}
|
||||
|
||||
void vtkGeantScene::AddToViewer(Viewport &viewer) {
|
||||
if (m_WorldPuppet) {
|
||||
viewer.AddPuppet(*m_WorldPuppet);
|
||||
}
|
||||
for (auto *p : m_SolidPuppets) {
|
||||
viewer.AddPuppet(*p);
|
||||
}
|
||||
}
|
||||
|
||||
void vtkGeantScene::RemoveFromViewer(Viewport &viewer) {
|
||||
if (m_WorldPuppet) {
|
||||
viewer.RemovePuppet(*m_WorldPuppet);
|
||||
}
|
||||
for (auto *p : m_SolidPuppets) {
|
||||
viewer.RemovePuppet(*p);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
87
src/Vtk/HEP/Geant/vtkGeantScene.h
Normal file
87
src/Vtk/HEP/Geant/vtkGeantScene.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef U_VTKGEANTSCENE_H
|
||||
#define U_VTKGEANTSCENE_H
|
||||
|
||||
#include "HEP/Geant/Scene.h"
|
||||
#include "uLibVtkInterface.h"
|
||||
#include "Vtk/Math/vtkContainerBox.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
class vtkGeantSolid;
|
||||
|
||||
/**
|
||||
* @brief VTK Puppet representing the entire Geant::Scene.
|
||||
*
|
||||
* When constructed, it creates child puppets for the world box (as a
|
||||
* vtkContainerBox wireframe) and for each non-world Solid in the scene
|
||||
* (as vtkGeantSolid surfaces).
|
||||
*
|
||||
* Usage:
|
||||
* @code
|
||||
* Geant::Scene scene;
|
||||
* scene.ConstructWorldBox(Vector3f(30_m, 30_m, 30_m), "G4_AIR");
|
||||
* // ... add solids ...
|
||||
* scene.Initialize();
|
||||
*
|
||||
* Vtk::Viewer viewer;
|
||||
* Vtk::vtkGeantScene vtkScene(&scene);
|
||||
* vtkScene.AddToViewer(viewer);
|
||||
* viewer.Start();
|
||||
* @endcode
|
||||
*/
|
||||
class vtkGeantScene : public Object {
|
||||
public:
|
||||
vtkGeantScene(Geant::Scene *scene);
|
||||
~vtkGeantScene();
|
||||
|
||||
/// Add all puppets (world box + solids) to a viewer.
|
||||
void AddToViewer(class Viewport &viewer);
|
||||
|
||||
/// Remove all puppets from viewport.
|
||||
void RemoveFromViewer(class Viewport &viewer);
|
||||
|
||||
/// Get the world box puppet
|
||||
vtkContainerBox* GetWorldPuppet() const { return m_WorldPuppet; }
|
||||
|
||||
/// Get the solid puppets
|
||||
const std::vector<vtkGeantSolid*>& GetSolidPuppets() const { return m_SolidPuppets; }
|
||||
|
||||
private:
|
||||
Geant::Scene *m_Scene;
|
||||
vtkContainerBox *m_WorldPuppet;
|
||||
std::vector<vtkGeantSolid*> m_SolidPuppets;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_VTKGEANTSCENE_H
|
||||
165
src/Vtk/HEP/Geant/vtkGeantSolid.cpp
Normal file
165
src/Vtk/HEP/Geant/vtkGeantSolid.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "vtkGeantSolid.h"
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkPoints.h>
|
||||
#include <vtkCellArray.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkTransform.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
|
||||
#include <Geant4/G4VSolid.hh>
|
||||
#include <Geant4/G4Polyhedron.hh>
|
||||
#include <Geant4/G4VPhysicalVolume.hh>
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
vtkGeantSolid::vtkGeantSolid(Content *content)
|
||||
: m_SolidActor(vtkActor::New()), m_Content(content) {
|
||||
this->InstallPipe();
|
||||
}
|
||||
|
||||
vtkGeantSolid::~vtkGeantSolid() {
|
||||
m_SolidActor->Delete();
|
||||
}
|
||||
|
||||
vtkPolyData *vtkGeantSolid::GetPolyData() const {
|
||||
if (!m_SolidActor || !m_SolidActor->GetMapper())
|
||||
return NULL;
|
||||
return vtkPolyData::SafeDownCast(m_SolidActor->GetMapper()->GetInput());
|
||||
}
|
||||
|
||||
void vtkGeantSolid::Update() {
|
||||
this->UpdateGeometry();
|
||||
this->UpdateTransform();
|
||||
}
|
||||
|
||||
void vtkGeantSolid::UpdateGeometry() {
|
||||
if (!m_Content)
|
||||
return;
|
||||
|
||||
G4VSolid *g4solid = m_Content->GetG4Solid();
|
||||
if (!g4solid)
|
||||
return;
|
||||
|
||||
// Get the polyhedron tessellation from Geant4
|
||||
G4Polyhedron *polyhedron = g4solid->GetPolyhedron();
|
||||
if (!polyhedron)
|
||||
return;
|
||||
|
||||
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
|
||||
vtkSmartPointer<vtkCellArray> polys = vtkSmartPointer<vtkCellArray>::New();
|
||||
|
||||
// Extract vertices
|
||||
int nVertices = polyhedron->GetNoVertices();
|
||||
for (int i = 1; i <= nVertices; ++i) {
|
||||
G4Point3D vtx = polyhedron->GetVertex(i);
|
||||
points->InsertNextPoint(vtx.x(), vtx.y(), vtx.z());
|
||||
}
|
||||
|
||||
// Extract facets (polygons)
|
||||
int nFacets = polyhedron->GetNoFacets();
|
||||
for (int f = 1; f <= nFacets; ++f) {
|
||||
G4int nEdges;
|
||||
G4int iVertex[4]; // G4Polyhedron facets have at most 4 vertices
|
||||
// GetNextFacet returns edges; for quads nEdges=4, for triangles nEdges=3
|
||||
polyhedron->GetFacet(f, nEdges, iVertex);
|
||||
|
||||
vtkIdType ids[4];
|
||||
for (int e = 0; e < nEdges; ++e) {
|
||||
// G4Polyhedron vertices are 1-indexed; VTK expects 0-indexed
|
||||
ids[e] = static_cast<vtkIdType>(std::abs(iVertex[e]) - 1);
|
||||
}
|
||||
polys->InsertNextCell(nEdges, ids);
|
||||
}
|
||||
|
||||
vtkPolyData *polyData = GetPolyData();
|
||||
if (polyData) {
|
||||
polyData->SetPoints(points);
|
||||
polyData->SetPolys(polys);
|
||||
polyData->Modified();
|
||||
}
|
||||
}
|
||||
|
||||
void vtkGeantSolid::UpdateTransform() {
|
||||
if (!m_Content || !m_SolidActor)
|
||||
return;
|
||||
|
||||
// Apply the Geant4 transform (position/rotation) if placed
|
||||
if (m_Content->GetPhysical()) {
|
||||
auto *phys = m_Content->GetPhysical();
|
||||
G4ThreeVector pos = phys->GetTranslation();
|
||||
const G4RotationMatrix *rot = phys->GetRotation();
|
||||
|
||||
vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
|
||||
transform->Identity();
|
||||
transform->Translate(pos.x(), pos.y(), pos.z());
|
||||
|
||||
if (rot) {
|
||||
// G4RotationMatrix stores the inverse of the rotation for placement
|
||||
G4RotationMatrix invRot = rot->inverse();
|
||||
double elements[16] = {
|
||||
invRot.xx(), invRot.xy(), invRot.xz(), 0,
|
||||
invRot.yx(), invRot.yy(), invRot.yz(), 0,
|
||||
invRot.zx(), invRot.zy(), invRot.zz(), 0,
|
||||
0, 0, 0, 1
|
||||
};
|
||||
vtkSmartPointer<vtkMatrix4x4> mat = vtkSmartPointer<vtkMatrix4x4>::New();
|
||||
mat->DeepCopy(elements);
|
||||
transform->Concatenate(mat);
|
||||
}
|
||||
|
||||
m_SolidActor->SetUserTransform(transform);
|
||||
}
|
||||
}
|
||||
|
||||
void vtkGeantSolid::InstallPipe() {
|
||||
vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
|
||||
vtkSmartPointer<vtkPolyDataMapper> mapper =
|
||||
vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||
mapper->SetInputData(polyData);
|
||||
|
||||
m_SolidActor->SetMapper(mapper);
|
||||
|
||||
// Default look: semi-transparent blue surface
|
||||
m_SolidActor->GetProperty()->SetColor(0.4, 0.6, 0.9);
|
||||
m_SolidActor->GetProperty()->SetOpacity(0.3);
|
||||
m_SolidActor->GetProperty()->SetAmbient(0.5);
|
||||
m_SolidActor->GetProperty()->SetDiffuse(0.6);
|
||||
m_SolidActor->GetProperty()->SetSpecular(0.2);
|
||||
m_SolidActor->GetProperty()->SetEdgeVisibility(1);
|
||||
m_SolidActor->GetProperty()->SetEdgeColor(0.2, 0.3, 0.5);
|
||||
|
||||
this->SetProp(m_SolidActor);
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
69
src/Vtk/HEP/Geant/vtkGeantSolid.h
Normal file
69
src/Vtk/HEP/Geant/vtkGeantSolid.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef U_VTKGEANTSOLID_H
|
||||
#define U_VTKGEANTSOLID_H
|
||||
|
||||
#include "HEP/Geant/Solid.h"
|
||||
#include "uLibVtkInterface.h"
|
||||
#include "vtkPolydata.h"
|
||||
|
||||
class vtkActor;
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
/**
|
||||
* @brief VTK Puppet for visualizing a Geant::Solid.
|
||||
*
|
||||
* Renders the G4VSolid geometry as a tessellated polydata surface.
|
||||
* Works with BoxSolid, TessellatedSolid, or any Solid that provides
|
||||
* a valid G4VSolid via GetG4Solid().
|
||||
*/
|
||||
class vtkGeantSolid : public Puppet, public Polydata {
|
||||
typedef Geant::Solid Content;
|
||||
|
||||
public:
|
||||
vtkGeantSolid(Content *content);
|
||||
~vtkGeantSolid();
|
||||
|
||||
virtual class vtkPolyData *GetPolyData() const override;
|
||||
|
||||
virtual void Update() override;
|
||||
|
||||
virtual void UpdateGeometry();
|
||||
virtual void UpdateTransform();
|
||||
|
||||
protected:
|
||||
virtual void InstallPipe();
|
||||
|
||||
vtkActor *m_SolidActor;
|
||||
Content *m_Content;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_VTKGEANTSOLID_H
|
||||
62
src/Vtk/HEP/Geant/vtkTessellatedSolid.cpp
Normal file
62
src/Vtk/HEP/Geant/vtkTessellatedSolid.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Copyright (c) 2014, Universita' degli Studi Padova, INFN sez. di Padova
|
||||
All rights reserved
|
||||
|
||||
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "vtkTessellatedSolid.h"
|
||||
|
||||
#include <vtkPoints.h>
|
||||
#include <vtkCellArray.h>
|
||||
#include <vtkPolyData.h>
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
vtkTessellatedSolid::vtkTessellatedSolid(Geant::TessellatedSolid *content)
|
||||
: vtkGeantSolid(content), m_TessContent(content) {
|
||||
this->Update();
|
||||
}
|
||||
|
||||
vtkTessellatedSolid::~vtkTessellatedSolid() {}
|
||||
|
||||
void vtkTessellatedSolid::Update() {
|
||||
this->UpdateGeometry();
|
||||
this->UpdateTransform();
|
||||
}
|
||||
|
||||
void vtkTessellatedSolid::UpdateGeometry() {
|
||||
if (!m_TessContent || m_TessContent->GetMesh().Points().empty()) {
|
||||
// Fallback to base tessellation if no model mesh
|
||||
vtkGeantSolid::UpdateGeometry();
|
||||
return;
|
||||
}
|
||||
|
||||
const TriangleMesh &mesh = m_TessContent->GetMesh();
|
||||
|
||||
vtkNew<vtkPoints> points;
|
||||
for (const auto& pt : mesh.Points()) {
|
||||
points->InsertNextPoint(pt(0), pt(1), pt(2));
|
||||
}
|
||||
|
||||
vtkNew<vtkCellArray> polys;
|
||||
for (const auto& trg : mesh.Triangles()) {
|
||||
vtkIdType ids[3] = { (vtkIdType)trg(0), (vtkIdType)trg(1), (vtkIdType)trg(2) };
|
||||
polys->InsertNextCell(3, ids);
|
||||
}
|
||||
|
||||
vtkPolyData *poly = GetPolyData();
|
||||
if (poly) {
|
||||
poly->SetPoints(points);
|
||||
poly->SetPolys(polys);
|
||||
poly->Modified();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
38
src/Vtk/HEP/Geant/vtkTessellatedSolid.h
Normal file
38
src/Vtk/HEP/Geant/vtkTessellatedSolid.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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 >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef U_VTKTESSELLATEDSOLID_H
|
||||
#define U_VTKTESSELLATEDSOLID_H
|
||||
|
||||
#include "vtkGeantSolid.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
/**
|
||||
* @brief VTK Puppet for visualizing a Geant::TessellatedSolid.
|
||||
*/
|
||||
class vtkTessellatedSolid : public vtkGeantSolid {
|
||||
public:
|
||||
vtkTessellatedSolid(Geant::TessellatedSolid *content);
|
||||
virtual ~vtkTessellatedSolid();
|
||||
|
||||
virtual void Update() override;
|
||||
virtual void UpdateGeometry() override;
|
||||
|
||||
protected:
|
||||
Geant::TessellatedSolid *m_TessContent;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_VTKTESSELLATEDSOLID_H
|
||||
@@ -10,6 +10,7 @@ set(MATH_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.cpp
|
||||
PARENT_SCOPE)
|
||||
|
||||
set(MATH_HEADERS
|
||||
@@ -20,6 +21,7 @@ set(MATH_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkVoxImage.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.h
|
||||
PARENT_SCOPE)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
|
||||
@@ -7,6 +7,7 @@ set(TESTS
|
||||
vtkVoxImageInteractiveTest
|
||||
vtkContainerBoxTest
|
||||
vtkContainerBoxTest2
|
||||
vtkAssemblyTest
|
||||
)
|
||||
|
||||
set(LIBRARIES
|
||||
|
||||
@@ -1,109 +1,104 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
|
||||
// All rights reserved
|
||||
|
||||
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
|
||||
All rights reserved
|
||||
|
||||
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "Vtk/uLibVtkViewer.h"
|
||||
#include "Vtk/Math/vtkAssembly.h"
|
||||
#include "Vtk/Math/vtkCylinder.h"
|
||||
#include "Vtk/Math/vtkContainerBox.h"
|
||||
#include "Math/Assembly.h"
|
||||
#include "Math/Cylinder.h"
|
||||
#include "Math/ContainerBox.h"
|
||||
#include "Math/Cylinder.h"
|
||||
#include "Vtk/Math/vtkAssembly.h"
|
||||
#include "Vtk/vtkObjectsContext.h"
|
||||
#include "Vtk/uLibVtkViewer.h"
|
||||
#include "Math/Units.h"
|
||||
|
||||
#include <vtkActor.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkPropCollection.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace uLib;
|
||||
|
||||
/**
|
||||
* @brief This test verifies that uLib::Vtk::Assembly correctly visualizes a collection
|
||||
* of objects and that transformations applied to the assembly are propagated to its children.
|
||||
* It also checks that the assembly appears as a bounding box of its contents.
|
||||
*/
|
||||
int main() {
|
||||
std::cout << "Starting vtkAssemblyTest..." << std::endl;
|
||||
int main(int argc, char **argv) {
|
||||
bool interactive = (argc > 1 && std::string(argv[1]) == "-i");
|
||||
|
||||
// 1. Setup Core Geometry
|
||||
std::cout << " - Creating core Assembly..." << std::endl;
|
||||
uLib::Assembly* core_assembly = new uLib::Assembly();
|
||||
core_assembly->SetInstanceName("MainAssembly");
|
||||
// ---- 1. Build model objects ----
|
||||
ContainerBox box1;
|
||||
box1.Scale(Vector3f(1_m, 2_m, 0.5_m));
|
||||
box1.SetPosition(Vector3f(0, 0, 0));
|
||||
|
||||
// Add a box
|
||||
std::cout << " - Adding ChildBox (Red)..." << std::endl;
|
||||
ContainerBox* box = new ContainerBox();
|
||||
box->SetInstanceName("ChildBox");
|
||||
box->Translate(Vector3f(1.0, 0, 0));
|
||||
core_assembly->AddObject(box);
|
||||
ContainerBox box2;
|
||||
box2.Scale(Vector3f(0.5_m, 0.5_m, 3_m));
|
||||
box2.SetPosition(Vector3f(2_m, 0, 0));
|
||||
|
||||
// Add a cylinder
|
||||
// std::cout << " - Adding ChildCylinder (Blue)..." << std::endl;
|
||||
// Cylinder* cyl = new Cylinder(0.5, 2.0);
|
||||
// cyl->SetInstanceName("ChildCylinder");
|
||||
// cyl->Translate(Vector3f(-2.0, 0, 0));
|
||||
// core_assembly->AddObject(cyl);
|
||||
Cylinder cyl(0.3_m, 1.5_m, 1);
|
||||
cyl.SetPosition(Vector3f(0, 3_m, 0));
|
||||
|
||||
std::cout << " - Adding another box (Green)..." << std::endl;
|
||||
ContainerBox* box2 = new ContainerBox();
|
||||
box2->Scale(Vector3f(1.0, 1.0, 2.0));
|
||||
box2->SetInstanceName("ChildBox2");
|
||||
box2->Translate(Vector3f(0, 0, 1.0));
|
||||
core_assembly->AddObject(box2);
|
||||
// ---- 2. Create an Assembly and add objects ----
|
||||
Assembly assembly;
|
||||
assembly.AddObject(&box1);
|
||||
assembly.AddObject(&box2);
|
||||
assembly.AddObject(&cyl);
|
||||
assembly.SetShowBoundingBox(true);
|
||||
|
||||
// 2. Setup VTK Representation
|
||||
std::cout << " - Creating VTK Assembly representation..." << std::endl;
|
||||
Vtk::Assembly vtk_assembly(core_assembly);
|
||||
|
||||
// Find and colorized the children puppets
|
||||
Vtk::Puppet* p_box = vtk_assembly.GetPuppet(box);
|
||||
if (p_box) {
|
||||
p_box->SetColor(1.0, 0.0, 0.0); // Red
|
||||
} else {
|
||||
std::cerr << "Warning: Could not find puppet for box!" << std::endl;
|
||||
}
|
||||
// ---- 3. Apply a group transform ----
|
||||
assembly.SetPosition(Vector3f(1_m, 1_m, 0));
|
||||
|
||||
// Vtk::Puppet* p_cyl = vtk_assembly.GetPuppet(cyl);
|
||||
// if (p_cyl) {
|
||||
// p_cyl->SetColor(0.0, 0.0, 1.0); // Blue
|
||||
// } else {
|
||||
// std::cerr << "Warning: Could not find puppet for cylinder!" << std::endl;
|
||||
// }
|
||||
|
||||
Vtk::Puppet* p_box2 = vtk_assembly.GetPuppet(box2);
|
||||
if (p_box2) {
|
||||
p_box2->SetColor(0.0, 1.0, 0.0); // Green
|
||||
} else {
|
||||
std::cerr << "Warning: Could not find puppet for box2!" << std::endl;
|
||||
}
|
||||
|
||||
// 3. Test Transformation Propagation
|
||||
std::cout << " - Rotating Assembly 45 degrees around Z..." << std::endl;
|
||||
core_assembly->Rotate(45.0_deg, Vector3f::UnitZ());
|
||||
|
||||
std::cout << " - Translating Assembly up (+Y)..." << std::endl;
|
||||
core_assembly->Translate(Vector3f(0, 2.0, 0));
|
||||
|
||||
// Notify all puppets of the change
|
||||
core_assembly->Updated();
|
||||
|
||||
// 4. Run Visualization
|
||||
std::cout << "Starting viewer (close to exit)..." << std::endl;
|
||||
std::cout << "Expected: A white bounding box containing a red box and a blue cylinder, "
|
||||
<< "all rotated and translated as a group." << std::endl;
|
||||
// ---- 5. Visualize (create puppets to set properties) ----
|
||||
Vtk::Assembly vtkAsm(&assembly);
|
||||
|
||||
Vtk::Viewer viewer;
|
||||
viewer.AddPuppet(*p_box);
|
||||
viewer.AddPuppet(*p_box2);
|
||||
viewer.AddPuppet(vtk_assembly);
|
||||
viewer.Start();
|
||||
vtkAsm.AddToViewer(viewer); // This triggers puppet creation via ConnectRenderer which eventually calls Puppet::GetProp
|
||||
|
||||
// Explicitly update to ensure puppets exist and are added to assemblies
|
||||
vtkAsm.Update();
|
||||
|
||||
// Clean up
|
||||
delete core_assembly;
|
||||
delete box;
|
||||
delete box2;
|
||||
// delete cyl;
|
||||
// Use the child context to find child puppets and set colors
|
||||
if (auto* childCtx = vtkAsm.GetChildrenContext()) {
|
||||
auto setProps = [](Vtk::Puppet* p, float r, float g, float b) {
|
||||
if (!p) return;
|
||||
vtkPropCollection* props = p->GetProps();
|
||||
props->InitTraversal();
|
||||
for (int i=0; i < props->GetNumberOfItems(); ++i) {
|
||||
if (auto* actor = vtkActor::SafeDownCast(props->GetNextProp())) {
|
||||
actor->GetProperty()->SetColor(r, g, b);
|
||||
actor->GetProperty()->SetRepresentationToSurface();
|
||||
actor->GetProperty()->SetOpacity(0.5);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setProps(childCtx->GetPuppet(&box1), 1.0, 0.0, 0.0); // Red
|
||||
setProps(childCtx->GetPuppet(&box2), 0.0, 1.0, 0.0); // Green
|
||||
setProps(childCtx->GetPuppet(&cyl), 0.0, 0.0, 1.0); // Blue
|
||||
}
|
||||
|
||||
std::cout << "Puppets in viewport: " << viewer.getPuppets().size() << " (Expected 4: 1 assembly + 3 children)" << std::endl;
|
||||
|
||||
// ---- 4. Query the bounding box for terminal output ----
|
||||
Vector3f bbMin, bbMax;
|
||||
assembly.GetBoundingBox(bbMin, bbMax);
|
||||
std::cout << "Assembly bounding box:" << std::endl;
|
||||
std::cout << " min = " << bbMin.transpose() << std::endl;
|
||||
std::cout << " max = " << bbMax.transpose() << std::endl;
|
||||
|
||||
std::cout << "==================================================\n";
|
||||
std::cout << " vtkAssemblyTest\n";
|
||||
std::cout << " 2 boxes + 1 cylinder grouped in an assembly\n";
|
||||
std::cout << "==================================================" << std::endl;
|
||||
|
||||
if (interactive) {
|
||||
viewer.ZoomAuto();
|
||||
viewer.Start();
|
||||
} else {
|
||||
std::cout << "Non-interactive test passed." << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
210
src/Vtk/Math/vtkAssembly.cpp
Normal file
210
src/Vtk/Math/vtkAssembly.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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 >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#include "vtkAssembly.h" // uLib::Vtk::Assembly
|
||||
#include "Vtk/vtkObjectsContext.h"
|
||||
#include "Math/vtkDense.h"
|
||||
|
||||
#include <vtkAssembly.h> // VTK library ::vtkAssembly
|
||||
#include <vtkActor.h>
|
||||
#include <vtkCubeSource.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
#include <vtkPropCollection.h>
|
||||
#include <vtkNew.h>
|
||||
#include <vtkProp3D.h>
|
||||
#include <vtkPoints.h>
|
||||
#include <vtkCellArray.h>
|
||||
#include <vtkPolyData.h>
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
Assembly::Assembly(uLib::Assembly *content)
|
||||
: m_Content(content),
|
||||
m_ChildContext(nullptr),
|
||||
m_BBoxActor(nullptr),
|
||||
m_VtkAsm(nullptr),
|
||||
m_InUpdate(false),
|
||||
m_BlockUpdate(false) {
|
||||
this->InstallPipe();
|
||||
if (m_Content) {
|
||||
Object::connect(m_Content, &uLib::Assembly::Updated,
|
||||
this, &Assembly::contentUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
Assembly::~Assembly() {
|
||||
delete m_ChildContext;
|
||||
if (m_BBoxActor) m_BBoxActor->Delete();
|
||||
if (m_VtkAsm) m_VtkAsm->Delete();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
void Assembly::InstallPipe() {
|
||||
// 1. Create the VTK library assembly that groups everything
|
||||
m_VtkAsm = ::vtkAssembly::New();
|
||||
this->SetProp(m_VtkAsm);
|
||||
|
||||
// 2. Create the bounding-box wireframe actor
|
||||
vtkNew<vtkCubeSource> cube;
|
||||
cube->SetBounds(0, 1, 0, 1, 0, 1);
|
||||
cube->Update();
|
||||
|
||||
vtkNew<vtkPolyDataMapper> mapper;
|
||||
mapper->SetInputConnection(cube->GetOutputPort());
|
||||
|
||||
m_BBoxActor = vtkActor::New();
|
||||
m_BBoxActor->SetMapper(mapper);
|
||||
m_BBoxActor->GetProperty()->SetRepresentationToWireframe();
|
||||
m_BBoxActor->GetProperty()->SetColor(1.0, 0.85, 0.0); // gold wireframe
|
||||
m_BBoxActor->GetProperty()->SetLineWidth(1.5);
|
||||
m_BBoxActor->GetProperty()->SetOpacity(0.6);
|
||||
m_BBoxActor->SetVisibility(m_Content ? m_Content->GetShowBoundingBox() : false);
|
||||
|
||||
m_VtkAsm->AddPart(m_BBoxActor);
|
||||
|
||||
// 3. Build a child-objects context (auto-creates puppets for each child)
|
||||
if (m_Content) {
|
||||
m_ChildContext = new vtkObjectsContext(m_Content);
|
||||
// The vtkObjectsContext's own prop is already a ::vtkAssembly;
|
||||
// nest it inside ours so everything moves together.
|
||||
if (auto *childProp = vtkProp3D::SafeDownCast(m_ChildContext->GetProp()))
|
||||
m_VtkAsm->AddPart(childProp);
|
||||
}
|
||||
|
||||
// 4. Apply initial transform
|
||||
this->UpdateTransform();
|
||||
this->UpdateBoundingBox();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
void Assembly::contentUpdate() {
|
||||
if (m_InUpdate) return;
|
||||
m_InUpdate = true;
|
||||
|
||||
this->UpdateTransform();
|
||||
this->UpdateBoundingBox();
|
||||
if (m_ChildContext)
|
||||
m_ChildContext->Update();
|
||||
|
||||
m_BlockUpdate = true;
|
||||
Puppet::Update();
|
||||
m_InUpdate = false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
void Assembly::Update() {
|
||||
if (m_InUpdate) return;
|
||||
if (!m_Content || !m_VtkAsm) return;
|
||||
|
||||
if (m_BlockUpdate) {
|
||||
m_BlockUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_InUpdate = true;
|
||||
|
||||
// Pull VTK transform back into the uLib model
|
||||
vtkMatrix4x4* vmat = m_VtkAsm->GetUserMatrix();
|
||||
if (vmat) {
|
||||
Matrix4f transform = VtkToMatrix4f(vmat);
|
||||
m_Content->SetMatrix(transform);
|
||||
}
|
||||
|
||||
this->UpdateBoundingBox();
|
||||
if (m_ChildContext)
|
||||
m_ChildContext->Update();
|
||||
|
||||
m_Content->Updated(); // Notify change in model
|
||||
|
||||
m_InUpdate = false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
void Assembly::UpdateTransform() {
|
||||
if (!m_Content || !m_VtkAsm) return;
|
||||
|
||||
Matrix4f mat = m_Content->GetMatrix();
|
||||
vtkNew<vtkMatrix4x4> vmat;
|
||||
Matrix4fToVtk(mat, vmat);
|
||||
m_VtkAsm->SetUserMatrix(vmat);
|
||||
m_VtkAsm->Modified();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
void Assembly::UpdateBoundingBox() {
|
||||
if (!m_Content || !m_BBoxActor) return;
|
||||
|
||||
m_BBoxActor->SetVisibility(m_Content->GetShowBoundingBox());
|
||||
|
||||
Vector3f bbMin, bbMax;
|
||||
m_Content->GetBoundingBox(bbMin, bbMax);
|
||||
|
||||
// Avoid degenerate boxes
|
||||
Vector3f size = bbMax - bbMin;
|
||||
if (size.norm() < 1e-6f) return;
|
||||
|
||||
// Rebuild the corner segments to match the AABB
|
||||
vtkNew<vtkPoints> points;
|
||||
vtkNew<vtkCellArray> lines;
|
||||
|
||||
float bounds[2][3] = {
|
||||
{bbMin(0), bbMin(1), bbMin(2)},
|
||||
{bbMax(0), bbMax(1), bbMax(2)}
|
||||
};
|
||||
|
||||
// Corner segment length: 10% of dimension
|
||||
float len[3] = {
|
||||
(bbMax(0) - bbMin(0)) * 0.1f,
|
||||
(bbMax(1) - bbMin(1)) * 0.1f,
|
||||
(bbMax(2) - bbMin(2)) * 0.1f
|
||||
};
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
float p[3];
|
||||
p[0] = bounds[(i & 1) ? 1 : 0][0];
|
||||
p[1] = bounds[(i & 2) ? 1 : 0][1];
|
||||
p[2] = bounds[(i & 4) ? 1 : 0][2];
|
||||
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
float p2[3] = {p[0], p[1], p[2]};
|
||||
float 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<vtkPolyData> poly;
|
||||
poly->SetPoints(points);
|
||||
poly->SetLines(lines);
|
||||
|
||||
vtkNew<vtkPolyDataMapper> mapper;
|
||||
mapper->SetInputData(poly);
|
||||
|
||||
m_BBoxActor->SetMapper(mapper);
|
||||
m_BBoxActor->Modified();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
vtkObjectsContext *Assembly::GetChildrenContext() const {
|
||||
return m_ChildContext;
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
71
src/Vtk/Math/vtkAssembly.h
Normal file
71
src/Vtk/Math/vtkAssembly.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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 >
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef U_VTK_ASSEMBLY_H
|
||||
#define U_VTK_ASSEMBLY_H
|
||||
|
||||
#include "Math/Assembly.h"
|
||||
#include "uLibVtkInterface.h"
|
||||
|
||||
class vtkActor;
|
||||
class vtkAssembly; // VTK library forward declaration (must be before namespace)
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
class vtkObjectsContext; // forward
|
||||
|
||||
/**
|
||||
* @brief VTK Puppet for visualizing uLib::Assembly.
|
||||
*
|
||||
* Manages a VTK assembly (vtkAssembly from the VTK library) that groups
|
||||
* all child puppets and applies the Assembly's AffineTransform. It also
|
||||
* renders an optional bounding box wireframe computed from the Assembly's AABB.
|
||||
*
|
||||
* @note This class is uLib::Vtk::Assembly. It internally uses
|
||||
* the VTK library class vtkAssembly for grouping, but the two
|
||||
* are distinct.
|
||||
*/
|
||||
class Assembly : public Puppet {
|
||||
public:
|
||||
virtual const char *GetClassName() const override { return "Vtk.Assembly"; }
|
||||
|
||||
Assembly(uLib::Assembly *content);
|
||||
virtual ~Assembly();
|
||||
|
||||
/** @brief Updates the VTK representation from the model (model→VTK). */
|
||||
virtual void Update() override;
|
||||
|
||||
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
|
||||
|
||||
/** @brief Called when the model signals an update (model→VTK push). */
|
||||
void contentUpdate();
|
||||
|
||||
/** @brief Returns the puppet managing child objects. */
|
||||
vtkObjectsContext *GetChildrenContext() const;
|
||||
|
||||
private:
|
||||
void UpdateTransform();
|
||||
void UpdateBoundingBox();
|
||||
void InstallPipe();
|
||||
|
||||
uLib::Assembly *m_Content;
|
||||
vtkObjectsContext *m_ChildContext;
|
||||
vtkActor *m_BBoxActor;
|
||||
::vtkAssembly *m_VtkAsm; // VTK library assembly — NOT this class
|
||||
bool m_InUpdate; // re-entrancy guard
|
||||
bool m_BlockUpdate; // block feedback from contentUpdate→Update
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
#endif // U_VTK_ASSEMBLY_H
|
||||
@@ -47,18 +47,23 @@
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
struct ContainerBoxData {
|
||||
vtkSmartPointer<vtkActor> m_Cube;
|
||||
vtkSmartPointer<vtkActor> m_Axes;
|
||||
|
||||
ContainerBoxData() : m_Cube(vtkSmartPointer<vtkActor>::New()), m_Axes(vtkSmartPointer<vtkActor>::New()) {}
|
||||
~ContainerBoxData() {
|
||||
}
|
||||
};
|
||||
|
||||
vtkContainerBox::vtkContainerBox(vtkContainerBox::Content *content)
|
||||
: m_Cube(vtkActor::New()), m_Axes(vtkActor::New()),
|
||||
// m_Pivot(vtkActor::New()),
|
||||
m_Content(content) {
|
||||
: d(new ContainerBoxData()), m_Content(content) {
|
||||
this->InstallPipe();
|
||||
Object::connect(m_Content, &Content::Updated, this, &vtkContainerBox::contentUpdate);
|
||||
}
|
||||
|
||||
vtkContainerBox::~vtkContainerBox() {
|
||||
m_Cube->Delete();
|
||||
m_Axes->Delete();
|
||||
// m_Pivot->Delete();
|
||||
delete d;
|
||||
}
|
||||
|
||||
vtkPolyData *vtkContainerBox::GetPolyData() const {
|
||||
@@ -83,8 +88,8 @@ void vtkContainerBox::contentUpdate() {
|
||||
vmat = mat;
|
||||
}
|
||||
|
||||
m_Cube->SetUserMatrix(nullptr);
|
||||
m_Axes->SetUserMatrix(nullptr);
|
||||
d->m_Cube->SetUserMatrix(nullptr);
|
||||
d->m_Axes->SetUserMatrix(nullptr);
|
||||
|
||||
Matrix4f transform = m_Content->GetMatrix();
|
||||
Matrix4fToVtk(transform, vmat);
|
||||
@@ -143,9 +148,9 @@ void vtkContainerBox::InstallPipe() {
|
||||
cube->SetBounds(0, 1, 0, 1, 0, 1);
|
||||
mapper->SetInputConnection(cube->GetOutputPort());
|
||||
mapper->Update();
|
||||
m_Cube->SetMapper(mapper);
|
||||
m_Cube->GetProperty()->SetRepresentationToWireframe();
|
||||
m_Cube->GetProperty()->SetAmbient(0.7);
|
||||
d->m_Cube->SetMapper(mapper);
|
||||
d->m_Cube->GetProperty()->SetRepresentationToWireframe();
|
||||
d->m_Cube->GetProperty()->SetAmbient(0.7);
|
||||
|
||||
// AXES //
|
||||
vtkSmartPointer<vtkAxes> axes = vtkSmartPointer<vtkAxes>::New();
|
||||
@@ -153,10 +158,10 @@ void vtkContainerBox::InstallPipe() {
|
||||
mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||
mapper->SetInputConnection(axes->GetOutputPort());
|
||||
mapper->Update();
|
||||
m_Axes->SetMapper(mapper);
|
||||
m_Axes->GetProperty()->SetLineWidth(3);
|
||||
m_Axes->GetProperty()->SetAmbient(0.4);
|
||||
m_Axes->GetProperty()->SetSpecular(0);
|
||||
d->m_Axes->SetMapper(mapper);
|
||||
d->m_Axes->GetProperty()->SetLineWidth(3);
|
||||
d->m_Axes->GetProperty()->SetAmbient(0.4);
|
||||
d->m_Axes->GetProperty()->SetSpecular(0);
|
||||
|
||||
// PIVOT //
|
||||
axes = vtkSmartPointer<vtkAxes>::New();
|
||||
@@ -165,8 +170,8 @@ void vtkContainerBox::InstallPipe() {
|
||||
mapper->SetInputConnection(axes->GetOutputPort());
|
||||
mapper->Update();
|
||||
|
||||
this->SetProp(m_Cube);
|
||||
this->SetProp(m_Axes);
|
||||
this->SetProp(d->m_Cube);
|
||||
this->SetProp(d->m_Axes);
|
||||
|
||||
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
|
||||
if (root) {
|
||||
|
||||
@@ -29,12 +29,15 @@
|
||||
#include "Math/ContainerBox.h"
|
||||
#include "uLibVtkInterface.h"
|
||||
#include "vtkPolydata.h"
|
||||
#include <vtkActor.h>
|
||||
#include <boost/signals2/connection.hpp>
|
||||
|
||||
class vtkActor;
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
struct ContainerBoxData;
|
||||
|
||||
class vtkContainerBox : public Puppet, public Polydata {
|
||||
typedef ContainerBox Content;
|
||||
|
||||
@@ -48,16 +51,14 @@ public:
|
||||
|
||||
virtual void Update();
|
||||
|
||||
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
|
||||
|
||||
protected:
|
||||
virtual void InstallPipe();
|
||||
|
||||
vtkActor *m_Cube;
|
||||
vtkActor *m_Axes;
|
||||
// vtkActor *m_Pivot;
|
||||
|
||||
Content *m_Content;
|
||||
bool m_BlockUpdate = false;
|
||||
// boost::signals2::connection m_Connection;
|
||||
struct ContainerBoxData *d;
|
||||
Content *m_Content;
|
||||
bool m_BlockUpdate = false;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "Vtk/Math/vtkCylinder.h"
|
||||
#include <vtkActor.h>
|
||||
#include <vtkAssembly.h>
|
||||
#include <vtkCylinderSource.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
@@ -37,13 +38,14 @@ namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
vtkCylinder::vtkCylinder(vtkCylinder::Content *content)
|
||||
: m_Actor(vtkActor::New()), m_Content(content) {
|
||||
: m_Content(content), m_Actor(nullptr), m_VtkAsm(nullptr) {
|
||||
this->InstallPipe();
|
||||
Object::connect(m_Content, &Content::Updated, this, &vtkCylinder::contentUpdate);
|
||||
}
|
||||
|
||||
vtkCylinder::~vtkCylinder() {
|
||||
m_Actor->Delete();
|
||||
if (m_Actor) m_Actor->Delete();
|
||||
if (m_VtkAsm) m_VtkAsm->Delete();
|
||||
}
|
||||
|
||||
void vtkCylinder::contentUpdate() {
|
||||
@@ -53,28 +55,31 @@ void vtkCylinder::contentUpdate() {
|
||||
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
|
||||
if (!root) return;
|
||||
|
||||
// 1. Placement (Position/Rotation/Model-level Scale) goes to the root prop
|
||||
vtkMatrix4x4* vmat = root->GetUserMatrix();
|
||||
if (!vmat) {
|
||||
vtkNew<vtkMatrix4x4> mat;
|
||||
root->SetUserMatrix(mat);
|
||||
vmat = mat;
|
||||
}
|
||||
|
||||
// Multiply the placement matrix by the volume scaling (Radius, Radius, Height)
|
||||
Matrix4f transform = m_Content->GetMatrix() * m_Content->GetLocalMatrix();
|
||||
Matrix4f transform = m_Content->GetMatrix();
|
||||
Matrix4fToVtk(transform, vmat);
|
||||
|
||||
// Update internal alignment based on active axis
|
||||
// 2. Shape-local properties (Radius, Height, Axis alignment) go to the internal actor
|
||||
vtkTransform* alignment = vtkTransform::SafeDownCast(m_Actor->GetUserTransform());
|
||||
if (alignment) {
|
||||
alignment->Identity();
|
||||
alignment->PostMultiply();
|
||||
|
||||
// Initial source is centered Y-cylinder (Radial XZ [-1,1], Height Y [-0.5, 0.5])
|
||||
// Apply Radius and Height scaling
|
||||
alignment->Scale(m_Content->GetRadius(), m_Content->GetHeight(), m_Content->GetRadius());
|
||||
|
||||
// Apply Axis alignment
|
||||
int axis = m_Content->GetAxis();
|
||||
if (axis == 0) alignment->RotateZ(-90); // Y -> X
|
||||
else if (axis == 1) ; // Y -> Y (identity)
|
||||
else if (axis == 2) alignment->RotateX(90); // Y -> Z
|
||||
|
||||
// We keep it centered as per latest user preference in Step 677
|
||||
// alignment->Translate(0, 0, 0); // Implicit
|
||||
}
|
||||
|
||||
root->Modified();
|
||||
@@ -90,16 +95,9 @@ void vtkCylinder::Update() {
|
||||
vtkMatrix4x4* vmat = root->GetUserMatrix();
|
||||
if (!vmat) return;
|
||||
|
||||
Matrix4f fullTransform = VtkToMatrix4f(vmat);
|
||||
Matrix4f placementScale = m_Content->GetLocalMatrix().inverse();
|
||||
Matrix4f transform = fullTransform * placementScale;
|
||||
|
||||
if (m_Content->GetParent()) {
|
||||
Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform;
|
||||
m_Content->SetMatrix(localT);
|
||||
} else {
|
||||
m_Content->SetMatrix(transform);
|
||||
}
|
||||
// Pull the placement matrix directly from VTK
|
||||
Matrix4f transform = VtkToMatrix4f(vmat);
|
||||
m_Content->SetMatrix(transform);
|
||||
|
||||
m_Content->Updated();
|
||||
}
|
||||
@@ -108,36 +106,27 @@ void vtkCylinder::InstallPipe() {
|
||||
if (!m_Content)
|
||||
return;
|
||||
|
||||
m_VtkAsm = ::vtkAssembly::New();
|
||||
this->SetProp(m_VtkAsm);
|
||||
|
||||
vtkNew<vtkCylinderSource> cylinder;
|
||||
cylinder->SetRadius(1.0);
|
||||
cylinder->SetHeight(1.0);
|
||||
cylinder->SetResolution(32);
|
||||
|
||||
m_Actor = vtkActor::New();
|
||||
vtkNew<vtkTransform> alignment;
|
||||
alignment->Identity();
|
||||
alignment->Translate(0, 0, -0.5);
|
||||
|
||||
// Default to Y alignment (Identity) as per latest request
|
||||
int axis = m_Content->GetAxis();
|
||||
if (axis == 0) alignment->RotateZ(-90);
|
||||
else if (axis == 2) alignment->RotateX(90);
|
||||
m_Actor->SetUserTransform(alignment);
|
||||
|
||||
vtkNew<vtkPolyDataMapper> mapper;
|
||||
mapper->SetInputConnection(cylinder->GetOutputPort());
|
||||
|
||||
m_Actor->SetMapper(mapper);
|
||||
m_Actor->SetUserTransform(alignment);
|
||||
m_Actor->GetProperty()->SetRepresentationToWireframe();
|
||||
m_Actor->GetProperty()->SetAmbient(0.6);
|
||||
|
||||
this->SetProp(m_Actor);
|
||||
|
||||
vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp());
|
||||
if (root) {
|
||||
vtkNew<vtkMatrix4x4> vmat;
|
||||
Matrix4fToVtk(m_Content->GetMatrix() * m_Content->GetLocalMatrix(), vmat);
|
||||
root->SetUserMatrix(vmat);
|
||||
}
|
||||
m_VtkAsm->AddPart(m_Actor);
|
||||
|
||||
this->contentUpdate();
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "Math/Cylinder.h"
|
||||
#include "Vtk/uLibVtkInterface.h"
|
||||
#include <vtkActor.h>
|
||||
class vtkAssembly;
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
@@ -53,11 +54,14 @@ public:
|
||||
/** Synchronizes the uLib model matrix with the VTK actor (e.g., after UI manipulation) */
|
||||
virtual void Update();
|
||||
|
||||
virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; }
|
||||
|
||||
protected:
|
||||
/** Sets up the VTK visualization pipeline */
|
||||
virtual void InstallPipe();
|
||||
|
||||
vtkActor *m_Actor;
|
||||
::vtkAssembly *m_VtkAsm;
|
||||
Content *m_Content;
|
||||
};
|
||||
|
||||
|
||||
@@ -126,9 +126,11 @@ vtkVoxImage::vtkVoxImage(Content &content)
|
||||
: m_Content(content), m_Actor(vtkVolume::New()),
|
||||
m_Image(vtkImageData::New()), m_Outline(vtkCubeSource::New()),
|
||||
m_OutlineActor(vtkActor::New()),
|
||||
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6) {
|
||||
m_Reader(NULL), m_Writer(NULL), writer_factor(1.E6),
|
||||
m_Window(40/1.E6), m_Level(20/1.E6), m_ShadingPreset(0) {
|
||||
GetContent();
|
||||
InstallPipe();
|
||||
ULIB_ACTIVATE_DISPLAY_PROPERTIES;
|
||||
}
|
||||
|
||||
vtkVoxImage::~vtkVoxImage() {
|
||||
@@ -199,14 +201,15 @@ void vtkVoxImage::ReadFromXMLFile(const char *fname) {
|
||||
}
|
||||
|
||||
void vtkVoxImage::setShadingPreset(int blendType) {
|
||||
m_ShadingPreset = blendType;
|
||||
vtkSmartVolumeMapper *mapper = (vtkSmartVolumeMapper *)m_Actor->GetMapper();
|
||||
vtkVolumeProperty *property = m_Actor->GetProperty();
|
||||
|
||||
static vtkColorTransferFunction *colorFun = vtkColorTransferFunction::New();
|
||||
static vtkPiecewiseFunction *opacityFun = vtkPiecewiseFunction::New();
|
||||
|
||||
float window = 40 / writer_factor;
|
||||
float level = 20 / writer_factor;
|
||||
float window = m_Window;
|
||||
float level = m_Level;
|
||||
|
||||
property->SetColor(colorFun);
|
||||
property->SetScalarOpacity(opacityFun);
|
||||
@@ -281,13 +284,24 @@ void vtkVoxImage::SetRepresentation(Representation mode) {
|
||||
m_OutlineActor->SetVisibility(1);
|
||||
} else if (mode == Surface) {
|
||||
m_Actor->SetVisibility(1);
|
||||
m_OutlineActor->SetVisibility(0);
|
||||
m_OutlineActor->SetVisibility(1); // Keep outline visible as boundary
|
||||
} else {
|
||||
Puppet::SetRepresentation(mode);
|
||||
}
|
||||
}
|
||||
|
||||
void vtkVoxImage::serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version) {
|
||||
// Call base class if it has display properties
|
||||
Puppet::serialize_display(ar, version);
|
||||
|
||||
// Use the member variables if they are available
|
||||
ar & boost::serialization::make_hrp("Window", m_Window);
|
||||
ar & boost::serialization::make_hrp("Level", m_Level);
|
||||
ar & boost::serialization::make_hrp_enum("Shading", m_ShadingPreset, {"MIP", "Composite", "Composite Shaded", "MIP Bone", "MIP Hot", "Additive"});
|
||||
}
|
||||
|
||||
void vtkVoxImage::Update() {
|
||||
setShadingPreset(m_ShadingPreset);
|
||||
m_Actor->Update();
|
||||
m_Outline->SetBounds(m_Image->GetBounds());
|
||||
m_Outline->Update();
|
||||
|
||||
@@ -65,7 +65,8 @@ public:
|
||||
void setShadingPreset(int blendType = 2);
|
||||
void SetRepresentation(Representation mode);
|
||||
|
||||
void Update();
|
||||
void Update() override;
|
||||
void serialize_display(uLib::Archive::display_properties_archive & ar, const unsigned int version = 0) override;
|
||||
|
||||
protected:
|
||||
void InstallPipe();
|
||||
@@ -84,6 +85,7 @@ private:
|
||||
|
||||
float m_Window;
|
||||
float m_Level;
|
||||
int m_ShadingPreset;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -46,13 +46,14 @@ int main() {
|
||||
box.Scale(Vector3f(2.0, 3.0, 4.0));
|
||||
box.SetPosition(Vector3f(1.0, 1.0, 1.0));
|
||||
|
||||
// 3. Setup the Viewer
|
||||
Vtk::Viewer viewer;
|
||||
|
||||
// 2. Wrap it in a Vtk::vtkContainerBox (Vtk Puppet)
|
||||
Vtk::vtkContainerBox v_box(&box);
|
||||
v_box.SetRepresentation(Vtk::Puppet::Surface);
|
||||
v_box.SetOpacity(0.5);
|
||||
|
||||
// 3. Setup the Viewer
|
||||
Vtk::Viewer viewer;
|
||||
viewer.AddPuppet(v_box);
|
||||
|
||||
// 4. Create and setup the vtkHandlerWidget
|
||||
@@ -112,5 +113,6 @@ int main() {
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
handler->EnabledOff();
|
||||
END_TESTING;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include <cstdlib>
|
||||
|
||||
#include <Vtk/uLibVtkViewer.h>
|
||||
|
||||
@@ -63,9 +64,10 @@ int main()
|
||||
actor->SetMapper(mapper);
|
||||
|
||||
v_viewer.addProp(actor);
|
||||
v_viewer.GetRenderer()->Render();
|
||||
v_viewer.Start();
|
||||
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
v_viewer.GetRenderer()->Render();
|
||||
v_viewer.Start();
|
||||
}
|
||||
|
||||
END_TESTING;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
|
||||
#include <string>
|
||||
#include <vtkVersion.h>
|
||||
#include <vtkProp.h>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkSmartPointer.h>
|
||||
|
||||
#include "vtkViewport.h"
|
||||
#include "uLibVtkInterface.h"
|
||||
#include <vtkActor.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkPropCollection.h>
|
||||
#include <vtkProp3DCollection.h>
|
||||
#include <vtkRendererCollection.h>
|
||||
@@ -57,9 +57,11 @@
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkFeatureEdges.h>
|
||||
#include <vtkTransform.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
|
||||
#include "uLibVtkInterface.h"
|
||||
#include "vtkHandlerWidget.h"
|
||||
#include "Math/Dense.h"
|
||||
#include "Core/Property.h"
|
||||
|
||||
|
||||
@@ -87,12 +89,17 @@ public:
|
||||
m_Assembly(vtkSmartPointer<vtkAssembly>::New()),
|
||||
m_ShowBoundingBox(false),
|
||||
m_ShowScaleMeasures(false),
|
||||
m_Representation(-1),
|
||||
m_Representation(Puppet::Surface),
|
||||
m_Opacity(-1.0),
|
||||
m_Selectable(true),
|
||||
m_Selected(false)
|
||||
m_Selected(false),
|
||||
m_Visibility(true),
|
||||
m_Dragable(true)
|
||||
{
|
||||
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
|
||||
m_Position = Vector3d::Zero();
|
||||
m_Orientation = Vector3d::Zero();
|
||||
m_Scale = Vector3d::Ones();
|
||||
}
|
||||
|
||||
~PuppetData() {
|
||||
@@ -110,25 +117,49 @@ public:
|
||||
|
||||
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;
|
||||
Vector3d m_Position;
|
||||
Vector3d m_Orientation;
|
||||
Vector3d m_Scale;
|
||||
|
||||
void ApplyAppearance(vtkProp *p) {
|
||||
p->SetVisibility(m_Visibility);
|
||||
p->SetPickable(m_Selectable);
|
||||
p->SetDragable(m_Dragable);
|
||||
|
||||
vtkActor *actor = vtkActor::SafeDownCast(p);
|
||||
if (!actor) return;
|
||||
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_Representation != -1) {
|
||||
actor->GetProperty()->SetRepresentation(m_Representation);
|
||||
if (m_Opacity != -1.0) {
|
||||
actor->GetProperty()->SetOpacity(m_Opacity);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Color[0] != -1.0) {
|
||||
actor->GetProperty()->SetColor(m_Color);
|
||||
}
|
||||
|
||||
if (m_Opacity != -1.0) {
|
||||
actor->GetProperty()->SetOpacity(m_Opacity);
|
||||
// Handle transformation if it's a Prop3D
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(p)) {
|
||||
// NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation
|
||||
// p3d->SetPosition(m_Position.data());
|
||||
// p3d->SetOrientation(m_Orientation.data());
|
||||
// p3d->SetScale(m_Scale.data());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,9 +219,6 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool m_Selectable;
|
||||
bool m_Selected;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
@@ -198,7 +226,7 @@ public:
|
||||
|
||||
|
||||
|
||||
Puppet::Puppet() : Object(), d(new PuppetData) {
|
||||
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);
|
||||
@@ -207,25 +235,40 @@ Puppet::Puppet() : Object(), d(new PuppetData) {
|
||||
|
||||
Puppet::~Puppet()
|
||||
{
|
||||
delete d;
|
||||
delete pd;
|
||||
}
|
||||
|
||||
vtkProp *Puppet::GetProp()
|
||||
{
|
||||
if (d->m_Assembly->GetParts()->GetNumberOfItems() == 1)
|
||||
return d->m_Assembly->GetParts()->GetLastProp();
|
||||
if (pd->m_Assembly->GetParts()->GetNumberOfItems() == 1)
|
||||
return pd->m_Assembly->GetParts()->GetLastProp();
|
||||
else
|
||||
return d->m_Assembly;
|
||||
return pd->m_Assembly;
|
||||
}
|
||||
|
||||
void Puppet::SetProp(vtkProp *prop)
|
||||
{
|
||||
if(prop) {
|
||||
prop->SetPickable(d->m_Selectable);
|
||||
prop->SetPickable(pd->m_Selectable);
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(prop)) {
|
||||
d->m_Assembly->AddPart(p3d);
|
||||
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);
|
||||
}
|
||||
}
|
||||
d->ApplyAppearance(prop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,12 +280,12 @@ void Puppet::RemoveProp(vtkProp *prop)
|
||||
|
||||
vtkPropCollection *Puppet::GetParts()
|
||||
{
|
||||
return d->m_Assembly->GetParts();
|
||||
return pd->m_Assembly->GetParts();
|
||||
}
|
||||
|
||||
vtkPropCollection *Puppet::GetProps()
|
||||
{
|
||||
return d->m_Assembly->GetParts();
|
||||
return pd->m_Assembly->GetParts();
|
||||
}
|
||||
|
||||
void Puppet::ConnectRenderer(vtkRenderer *renderer)
|
||||
@@ -253,14 +296,14 @@ void Puppet::ConnectRenderer(vtkRenderer *renderer)
|
||||
renderer->AddViewProp(prop);
|
||||
}
|
||||
|
||||
if (d->m_ShowBoundingBox && d->m_OutlineActor) renderer->AddActor(d->m_OutlineActor);
|
||||
if (d->m_ShowScaleMeasures && d->m_CubeAxesActor) {
|
||||
d->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera());
|
||||
renderer->AddActor(d->m_CubeAxesActor);
|
||||
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 (d->m_Selected && d->m_HighlightActor) {
|
||||
renderer->AddActor(d->m_HighlightActor);
|
||||
if (pd->m_Selected && pd->m_HighlightActor) {
|
||||
renderer->AddActor(pd->m_HighlightActor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,66 +314,66 @@ void Puppet::DisconnectRenderer(vtkRenderer *renderer)
|
||||
if(vtkProp* prop = this->GetProp())
|
||||
renderer->RemoveViewProp(prop);
|
||||
|
||||
if (d->m_ShowBoundingBox && d->m_OutlineActor) renderer->RemoveActor(d->m_OutlineActor);
|
||||
if (d->m_ShowScaleMeasures && d->m_CubeAxesActor) renderer->RemoveActor(d->m_CubeAxesActor);
|
||||
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::ConnectViewer(Viewer *viewer)
|
||||
void Puppet::AddToViewer(Viewport &viewer)
|
||||
{
|
||||
// TODO
|
||||
viewer.AddPuppet(*this);
|
||||
}
|
||||
|
||||
void Puppet::DisonnectViewer(Viewer *viewer)
|
||||
void Puppet::RemoveFromViewer(Viewport &viewer)
|
||||
{
|
||||
// TODO
|
||||
viewer.RemovePuppet(*this);
|
||||
}
|
||||
|
||||
vtkRendererCollection *Puppet::GetRenderers() const
|
||||
{
|
||||
return d->m_Renderers;
|
||||
return pd->m_Renderers;
|
||||
}
|
||||
|
||||
void Puppet::PrintSelf(std::ostream &o) const
|
||||
{
|
||||
o << "Props Assembly: \n";
|
||||
d->m_Assembly->PrintSelf(o,vtkIndent(1));
|
||||
pd->m_Assembly->PrintSelf(o,vtkIndent(1));
|
||||
|
||||
o << "Connected Renderers: \n";
|
||||
d->m_Renderers->PrintSelf(o,vtkIndent(1));
|
||||
pd->m_Renderers->PrintSelf(o,vtkIndent(1));
|
||||
}
|
||||
|
||||
void Puppet::ShowBoundingBox(bool show)
|
||||
{
|
||||
if (d->m_ShowBoundingBox == show) return;
|
||||
d->m_ShowBoundingBox = show;
|
||||
if (pd->m_ShowBoundingBox == show) return;
|
||||
pd->m_ShowBoundingBox = show;
|
||||
if (show) {
|
||||
if (!d->m_OutlineActor) {
|
||||
d->m_OutlineSource = vtkSmartPointer<vtkOutlineSource>::New();
|
||||
d->m_OutlineActor = vtkSmartPointer<vtkActor>::New();
|
||||
if (!pd->m_OutlineActor) {
|
||||
pd->m_OutlineSource = vtkSmartPointer<vtkOutlineSource>::New();
|
||||
pd->m_OutlineActor = vtkSmartPointer<vtkActor>::New();
|
||||
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
||||
mapper->SetInputConnection(d->m_OutlineSource->GetOutputPort());
|
||||
d->m_OutlineActor->SetMapper(mapper);
|
||||
d->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
||||
mapper->SetInputConnection(pd->m_OutlineSource->GetOutputPort());
|
||||
pd->m_OutlineActor->SetMapper(mapper);
|
||||
pd->m_OutlineActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
double* bounds = d->m_Assembly->GetBounds();
|
||||
d->m_OutlineSource->SetBounds(bounds);
|
||||
d->m_OutlineSource->Update();
|
||||
double* bounds = pd->m_Assembly->GetBounds();
|
||||
pd->m_OutlineSource->SetBounds(bounds);
|
||||
pd->m_OutlineSource->Update();
|
||||
|
||||
d->m_Renderers->InitTraversal();
|
||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
||||
renderer->AddActor(d->m_OutlineActor);
|
||||
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 (d->m_OutlineActor) {
|
||||
d->m_Renderers->InitTraversal();
|
||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
||||
renderer->RemoveActor(d->m_OutlineActor);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,31 +381,31 @@ void Puppet::ShowBoundingBox(bool show)
|
||||
|
||||
void Puppet::ShowScaleMeasures(bool show)
|
||||
{
|
||||
if (d->m_ShowScaleMeasures == show) return;
|
||||
d->m_ShowScaleMeasures = show;
|
||||
if (pd->m_ShowScaleMeasures == show) return;
|
||||
pd->m_ShowScaleMeasures = show;
|
||||
if (show) {
|
||||
if (!d->m_CubeAxesActor) {
|
||||
d->m_CubeAxesActor = vtkSmartPointer<vtkCubeAxesActor>::New();
|
||||
d->m_CubeAxesActor->SetFlyModeToOuterEdges();
|
||||
d->m_CubeAxesActor->SetUseTextActor3D(1);
|
||||
d->m_CubeAxesActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
||||
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 = d->m_Assembly->GetBounds();
|
||||
d->m_CubeAxesActor->SetBounds(bounds);
|
||||
double* bounds = pd->m_Assembly->GetBounds();
|
||||
pd->m_CubeAxesActor->SetBounds(bounds);
|
||||
|
||||
d->m_Renderers->InitTraversal();
|
||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
||||
d->m_CubeAxesActor->SetCamera(renderer->GetActiveCamera());
|
||||
renderer->AddActor(d->m_CubeAxesActor);
|
||||
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 (d->m_CubeAxesActor) {
|
||||
d->m_Renderers->InitTraversal();
|
||||
for (int i = 0; i < d->m_Renderers->GetNumberOfItems(); ++i) {
|
||||
vtkRenderer *renderer = d->m_Renderers->GetNextItem();
|
||||
renderer->RemoveActor(d->m_CubeAxesActor);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,18 +413,12 @@ void Puppet::ShowScaleMeasures(bool show)
|
||||
|
||||
void Puppet::SetRepresentation(Representation mode)
|
||||
{
|
||||
int rep = VTK_SURFACE;
|
||||
switch (mode) {
|
||||
case Points: rep = VTK_POINTS; break;
|
||||
case Wireframe: rep = VTK_WIREFRAME; break;
|
||||
case Surface: rep = VTK_SURFACE; break;
|
||||
}
|
||||
d->m_Representation = rep;
|
||||
pd->m_Representation = static_cast<int>(mode);
|
||||
|
||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
||||
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||
props->InitTraversal();
|
||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||
d->ApplyAppearance(props->GetNextProp3D());
|
||||
pd->ApplyAppearance(props->GetNextProp3D());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,29 +428,33 @@ void Puppet::SetRepresentation(const char *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)
|
||||
{
|
||||
d->m_Color[0] = r;
|
||||
d->m_Color[1] = g;
|
||||
d->m_Color[2] = b;
|
||||
pd->m_Color[0] = r;
|
||||
pd->m_Color[1] = g;
|
||||
pd->m_Color[2] = b;
|
||||
|
||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
||||
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||
props->InitTraversal();
|
||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||
d->ApplyAppearance(props->GetNextProp3D());
|
||||
pd->ApplyAppearance(props->GetNextProp3D());
|
||||
}
|
||||
}
|
||||
|
||||
void Puppet::SetOpacity(double alpha)
|
||||
{
|
||||
d->m_Opacity = alpha;
|
||||
pd->m_Opacity = alpha;
|
||||
|
||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
||||
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||
props->InitTraversal();
|
||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||
d->ApplyAppearance(props->GetNextProp3D());
|
||||
pd->ApplyAppearance(props->GetNextProp3D());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,8 +466,8 @@ void Puppet::SetOpacity(double alpha)
|
||||
|
||||
void Puppet::SetSelectable(bool selectable)
|
||||
{
|
||||
d->m_Selectable = selectable;
|
||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
||||
pd->m_Selectable = selectable;
|
||||
vtkProp3DCollection *props = pd->m_Assembly->GetParts();
|
||||
props->InitTraversal();
|
||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||
props->GetNextProp3D()->SetPickable(selectable);
|
||||
@@ -435,43 +476,91 @@ void Puppet::SetSelectable(bool selectable)
|
||||
|
||||
bool Puppet::IsSelectable() const
|
||||
{
|
||||
return d->m_Selectable;
|
||||
return pd->m_Selectable;
|
||||
}
|
||||
|
||||
void Puppet::SetSelected(bool selected)
|
||||
{
|
||||
if (!d->m_Selectable) return;
|
||||
if (d->m_Selected == selected) return;
|
||||
d->m_Selected = selected;
|
||||
d->UpdateHighlight();
|
||||
if (!pd->m_Selectable) return;
|
||||
if (pd->m_Selected == selected) return;
|
||||
pd->m_Selected = selected;
|
||||
pd->UpdateHighlight();
|
||||
}
|
||||
|
||||
bool Puppet::IsSelected() const
|
||||
{
|
||||
return d->m_Selected;
|
||||
return pd->m_Selected;
|
||||
}
|
||||
|
||||
void Puppet::Update()
|
||||
{
|
||||
vtkProp3DCollection *props = d->m_Assembly->GetParts();
|
||||
props->InitTraversal();
|
||||
for (int i = 0; i < props->GetNumberOfItems(); ++i) {
|
||||
d->ApplyAppearance(props->GetNextProp3D());
|
||||
vtkProp* root = this->GetProp();
|
||||
if (root) {
|
||||
pd->ApplyAppearance(root);
|
||||
|
||||
// Apply transformation if it's a Prop3D
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
|
||||
p3d->SetPosition(pd->m_Position.data());
|
||||
p3d->SetOrientation(pd->m_Orientation.data());
|
||||
p3d->SetScale(pd->m_Scale.data());
|
||||
}
|
||||
}
|
||||
|
||||
if (d->m_Selected) {
|
||||
d->UpdateHighlight();
|
||||
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 (d->m_ShowBoundingBox) {
|
||||
double* bounds = d->m_Assembly->GetBounds();
|
||||
d->m_OutlineSource->SetBounds(bounds);
|
||||
d->m_OutlineSource->Update();
|
||||
if (pd->m_ShowBoundingBox) {
|
||||
double* bounds = pd->m_Assembly->GetBounds();
|
||||
pd->m_OutlineSource->SetBounds(bounds);
|
||||
pd->m_OutlineSource->Update();
|
||||
}
|
||||
|
||||
if (d->m_ShowScaleMeasures) {
|
||||
double* bounds = d->m_Assembly->GetBounds();
|
||||
d->m_CubeAxesActor->SetBounds(bounds);
|
||||
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)) {
|
||||
double pos[3], ori[3], scale[3];
|
||||
p3d->GetPosition(pos);
|
||||
p3d->GetOrientation(ori);
|
||||
p3d->GetScale(scale);
|
||||
|
||||
// Update properties
|
||||
for (int i=0; i<3; ++i) {
|
||||
pd->m_Position(i) = pos[i];
|
||||
pd->m_Orientation(i) = ori[i];
|
||||
pd->m_Scale(i) = scale[i];
|
||||
}
|
||||
|
||||
// Get the properties from the object
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,12 +568,37 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
|
||||
{
|
||||
}
|
||||
|
||||
struct TransformProxy {
|
||||
PuppetData* pd;
|
||||
template<class Archive>
|
||||
void serialize(Archive & ar, const unsigned int version) {
|
||||
ar & boost::serialization::make_hrp("Position", pd->m_Position, "mm");
|
||||
ar & boost::serialization::make_hrp("Orientation", pd->m_Orientation, "deg");
|
||||
ar & boost::serialization::make_hrp("Scale", pd->m_Scale, "");
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
ar & boost::serialization::make_hrp("ColorR", d->m_Color[0]);
|
||||
ar & boost::serialization::make_hrp("ColorG", d->m_Color[1]);
|
||||
ar & boost::serialization::make_hrp("ColorB", d->m_Color[2]);
|
||||
ar & boost::serialization::make_hrp("Opacity", d->m_Opacity);
|
||||
ar & boost::serialization::make_hrp("Representation", d->m_Representation);
|
||||
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) { }
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#include <iomanip>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include <boost/type_traits/is_class.hpp>
|
||||
#include <boost/mpl/bool.hpp>
|
||||
#include <boost/serialization/serialization.hpp>
|
||||
#include "Core/Object.h"
|
||||
#include "Core/Property.h"
|
||||
#include "Core/Monitor.h"
|
||||
@@ -61,6 +64,8 @@ public:
|
||||
|
||||
virtual vtkPropCollection *GetProps();
|
||||
|
||||
virtual uLib::Object *GetContent() const { return nullptr; }
|
||||
|
||||
void ConnectRenderer(vtkRenderer *renderer);
|
||||
|
||||
void DisconnectRenderer(vtkRenderer *renderer);
|
||||
@@ -80,8 +85,9 @@ public:
|
||||
bool IsSelected() const;
|
||||
|
||||
virtual void Update();
|
||||
virtual void SyncFromVtk();
|
||||
|
||||
enum Representation { Points, Wireframe, Surface };
|
||||
enum Representation { Points = 0, Wireframe = 1, Surface = 2, SurfaceWithEdges = 3, Volume = 4, Outline = 5, Slice = 6 };
|
||||
void SetRepresentation(Representation mode);
|
||||
void SetRepresentation(const char *mode);
|
||||
|
||||
@@ -107,6 +113,9 @@ public:
|
||||
|
||||
virtual void ConnectInteractor(class vtkRenderWindowInteractor *interactor);
|
||||
|
||||
void AddToViewer(class Viewport &viewer);
|
||||
void RemoveFromViewer(class Viewport &viewer);
|
||||
|
||||
protected:
|
||||
void SetProp(vtkProp *prop);
|
||||
|
||||
@@ -120,12 +129,23 @@ private:
|
||||
Puppet& operator=(const Puppet&) = delete;
|
||||
|
||||
friend class PuppetData;
|
||||
class PuppetData *d;
|
||||
class PuppetData *pd;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// DISPLAY PROPERTIES SERIALIZE
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
namespace uLib {
|
||||
@@ -140,20 +160,53 @@ public:
|
||||
boost::archive::detail::common_oarchive<display_properties_archive>(boost::archive::no_header),
|
||||
m_Puppet(puppet) {}
|
||||
|
||||
std::string GetCurrentGroup() const {
|
||||
std::string group;
|
||||
for (const auto& g : m_GroupStack) {
|
||||
if (!group.empty()) group += ".";
|
||||
group += g;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp<T> &t) {
|
||||
if (m_Puppet) {
|
||||
m_Puppet->RegisterDisplayProperty(
|
||||
new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value())
|
||||
);
|
||||
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
m_Puppet->RegisterDisplayProperty(p);
|
||||
Vtk::Puppet* puppet = m_Puppet;
|
||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||
if (m_Puppet) {
|
||||
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||
m_Puppet->RegisterDisplayProperty(p);
|
||||
Vtk::Puppet* puppet = m_Puppet;
|
||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> void save_override(const boost::serialization::nvp<T> &t) {
|
||||
boost::archive::detail::common_oarchive<display_properties_archive>::save_override(t.const_value());
|
||||
if (t.name()) m_GroupStack.push_back(t.name());
|
||||
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
||||
if (t.name()) m_GroupStack.pop_back();
|
||||
}
|
||||
|
||||
template<class T> void save_override(const T &t) {}
|
||||
// Recursion for nested classes, ignore primitives
|
||||
template<class T> void save_override(const T &t) {
|
||||
this->save_helper(t, typename boost::is_class<T>::type());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::true_) {
|
||||
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void save_helper(const T &t, boost::mpl::false_) {}
|
||||
|
||||
void save_override(const boost::archive::object_id_type & t) {}
|
||||
void save_override(const boost::archive::object_reference_type & t) {}
|
||||
@@ -166,6 +219,7 @@ public:
|
||||
|
||||
private:
|
||||
Vtk::Puppet* m_Puppet;
|
||||
std::vector<std::string> m_GroupStack;
|
||||
};
|
||||
|
||||
} // namespace Archive
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
#include <vtkRendererCollection.h>
|
||||
#include <vtkSmartPointer.h>
|
||||
@@ -67,53 +68,83 @@ vtkStandardNewMacro(vtkInteractorStyleNoSpin);
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
struct ViewerData {
|
||||
vtkRenderWindow *m_RenderWindow;
|
||||
vtkSmartPointer<vtkRenderWindowInteractor> m_Interactor;
|
||||
vtkSmartPointer<vtkButtonWidget> m_GridButton;
|
||||
|
||||
ViewerData() : m_RenderWindow(vtkRenderWindow::New()) {}
|
||||
~ViewerData() {
|
||||
if (m_Interactor) {
|
||||
m_Interactor->SetRenderWindow(nullptr);
|
||||
}
|
||||
m_RenderWindow->Delete();
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///// VTK VIEWER //////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Viewer::Viewer()
|
||||
: Viewport(), m_RenderWindow(vtkRenderWindow::New()) {
|
||||
: 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 (this->GetRenderWindow()) {
|
||||
this->GetRenderWindow()->RemoveAllObservers();
|
||||
}
|
||||
if (this->GetInteractor()) {
|
||||
this->GetInteractor()->RemoveAllObservers();
|
||||
}
|
||||
UninstallPipe();
|
||||
m_RenderWindow->Delete();
|
||||
delete dv;
|
||||
}
|
||||
|
||||
void Viewer::InstallPipe() {
|
||||
m_RenderWindow->AddRenderer(m_Renderer);
|
||||
m_RenderWindow->SetSize(600,600);
|
||||
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
|
||||
vtkSmartPointer<vtkRenderWindowInteractor>::New();
|
||||
renderWindowInteractor->SetRenderWindow(m_RenderWindow);
|
||||
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<vtkRenderWindowInteractor>::New();
|
||||
dv->m_Interactor->SetRenderWindow(dv->m_RenderWindow);
|
||||
|
||||
// Common setup
|
||||
Viewport::SetupPipeline(renderWindowInteractor);
|
||||
Viewport::SetupPipeline(dv->m_Interactor);
|
||||
|
||||
// Setup native grid button
|
||||
SetupGridButton();
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
SetupGridButton();
|
||||
}
|
||||
|
||||
// BUT we want to override the style with our custom NoSpin version
|
||||
vtkSmartPointer<vtkInteractorStyleNoSpin> style =
|
||||
vtkSmartPointer<vtkInteractorStyleNoSpin>::New();
|
||||
renderWindowInteractor->SetInteractorStyle(style);
|
||||
dv->m_Interactor->SetInteractorStyle(style);
|
||||
|
||||
// Must be rendered here in Vtk-6.0 or seg-fault //
|
||||
m_RenderWindow->Render();
|
||||
}
|
||||
|
||||
void Viewer::UninstallPipe() {
|
||||
if (m_Renderer) {
|
||||
m_Renderer->RemoveAllViewProps();
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
dv->m_RenderWindow->Render();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::Render() {
|
||||
if (m_RenderWindow)
|
||||
m_RenderWindow->Render();
|
||||
void Viewer::UninstallPipe() {
|
||||
}
|
||||
|
||||
vtkSmartPointer<vtkCameraOrientationWidget>
|
||||
void Viewer::Render() {
|
||||
if (dv->m_RenderWindow)
|
||||
dv->m_RenderWindow->Render();
|
||||
}
|
||||
|
||||
vtkCameraOrientationWidget *
|
||||
Viewer::MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
||||
vtkRenderer *renderer) {
|
||||
vtkSmartPointer<vtkCameraOrientationWidget> widget =
|
||||
@@ -125,7 +156,7 @@ Viewer::MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
||||
}
|
||||
|
||||
void Viewer::SetupGridButton() {
|
||||
if (!m_RenderWindow || !m_RenderWindow->GetInteractor()) return;
|
||||
if (!dv->m_RenderWindow || !dv->m_RenderWindow->GetInteractor()) return;
|
||||
|
||||
// Create procedural textures for the button using canvas
|
||||
vtkNew<vtkImageCanvasSource2D> canvas;
|
||||
@@ -158,9 +189,9 @@ void Viewer::SetupGridButton() {
|
||||
rep->SetButtonTexture(0, imgOff);
|
||||
rep->SetButtonTexture(1, imgOn);
|
||||
|
||||
m_GridButton = vtkSmartPointer<vtkButtonWidget>::New();
|
||||
m_GridButton->SetInteractor(m_RenderWindow->GetInteractor());
|
||||
m_GridButton->SetRepresentation(rep);
|
||||
dv->m_GridButton = vtkSmartPointer<vtkButtonWidget>::New();
|
||||
dv->m_GridButton->SetInteractor(dv->m_RenderWindow->GetInteractor());
|
||||
dv->m_GridButton->SetRepresentation(rep);
|
||||
|
||||
// Position it initially
|
||||
UpdateGridButtonPosition();
|
||||
@@ -172,7 +203,7 @@ void Viewer::SetupGridButton() {
|
||||
auto* v = static_cast<Viewer*>(clientdata);
|
||||
v->UpdateGridButtonPosition();
|
||||
});
|
||||
m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback);
|
||||
dv->m_RenderWindow->AddObserver(vtkCommand::ModifiedEvent, resizeCallback);
|
||||
|
||||
// Callback for state change
|
||||
vtkNew<vtkCallbackCommand> stateCallback;
|
||||
@@ -184,19 +215,19 @@ void Viewer::SetupGridButton() {
|
||||
v->SetGridVisible(r->GetState() == 1);
|
||||
});
|
||||
|
||||
m_GridButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback);
|
||||
m_GridButton->On();
|
||||
dv->m_GridButton->AddObserver(vtkCommand::StateChangedEvent, stateCallback);
|
||||
dv->m_GridButton->On();
|
||||
|
||||
// Set initial state
|
||||
rep->SetState(GetGridVisible() ? 1 : 0);
|
||||
}
|
||||
|
||||
void Viewer::UpdateGridButtonPosition() {
|
||||
if (!m_GridButton || !m_RenderWindow) return;
|
||||
auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(m_GridButton->GetRepresentation());
|
||||
if (!dv->m_GridButton || !dv->m_RenderWindow) return;
|
||||
auto* rep = vtkTexturedButtonRepresentation2D::SafeDownCast(dv->m_GridButton->GetRepresentation());
|
||||
if (!rep) return;
|
||||
|
||||
int *sz = m_RenderWindow->GetSize();
|
||||
int *sz = dv->m_RenderWindow->GetSize();
|
||||
if (sz[0] == 0 || sz[1] == 0) return; // Window not yet sized or hidden
|
||||
|
||||
int margin_rigth = 23;
|
||||
@@ -207,12 +238,15 @@ void Viewer::UpdateGridButtonPosition() {
|
||||
rep->PlaceWidget(bds);
|
||||
}
|
||||
|
||||
void Viewer::Start() { m_RenderWindow->GetInteractor()->Start(); }
|
||||
void Viewer::Start() {
|
||||
if (std::getenv("CTEST_PROJECT_NAME")) return;
|
||||
dv->m_RenderWindow->GetInteractor()->Start();
|
||||
}
|
||||
|
||||
vtkRenderWindow *Viewer::GetRenderWindow() { return m_RenderWindow; }
|
||||
vtkRenderWindow *Viewer::GetRenderWindow() { return dv->m_RenderWindow; }
|
||||
|
||||
vtkRenderWindowInteractor *Viewer::GetInteractor() {
|
||||
return m_RenderWindow->GetInteractor();
|
||||
return dv->m_RenderWindow->GetInteractor();
|
||||
}
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
@@ -1,44 +1,17 @@
|
||||
/*//////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
#ifndef ULIBVTKVIEWER_H
|
||||
#define ULIBVTKVIEWER_H
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "vtkViewport.h"
|
||||
|
||||
class vtkRenderWindow;
|
||||
class vtkRenderWindowInteractor;
|
||||
class vtkRenderer;
|
||||
class vtkCameraOrientationWidget;
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
// template <class T> class Tie {
|
||||
// public:
|
||||
// void DoAction() {
|
||||
// std::cout << "Tie::DoAction -> generic Tie does nothing\n";
|
||||
// }
|
||||
// };
|
||||
struct ViewerData;
|
||||
|
||||
class Viewer : public Viewport {
|
||||
|
||||
@@ -49,9 +22,9 @@ public:
|
||||
// Render scene
|
||||
virtual void Render() override;
|
||||
|
||||
static vtkSmartPointer<vtkCameraOrientationWidget>
|
||||
static vtkCameraOrientationWidget *
|
||||
MakeCameraOrientationWidget(vtkRenderWindowInteractor *interactor,
|
||||
vtkRenderer *renderer);
|
||||
vtkRenderer *renderer);
|
||||
|
||||
void Start();
|
||||
|
||||
@@ -65,15 +38,9 @@ private:
|
||||
void SetupGridButton();
|
||||
void UpdateGridButtonPosition();
|
||||
|
||||
vtkRenderWindow *m_RenderWindow;
|
||||
vtkSmartPointer<class vtkButtonWidget> m_GridButton;
|
||||
struct ViewerData *dv;
|
||||
};
|
||||
|
||||
// template <> class Tie<Viewer> {
|
||||
// public:
|
||||
// void DoAction() { std::cout << " VIEWER TIE !!! \n"; }
|
||||
// };
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "vtkHandlerWidget.h"
|
||||
#include <iostream>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkArcSource.h>
|
||||
#include <vtkArrowSource.h>
|
||||
#include <vtkCallbackCommand.h>
|
||||
@@ -51,32 +52,61 @@
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
struct HandlerWidgetData {
|
||||
vtkSmartPointer<::vtkRenderer> m_OverlayRenderer;
|
||||
::vtkProp *m_HighlightedProp;
|
||||
|
||||
// Visual components //
|
||||
vtkSmartPointer<::vtkActor> m_AxesX, m_AxesY, m_AxesZ; // Arrows
|
||||
vtkSmartPointer<::vtkActor> m_RotX, m_RotY, m_RotZ; // Rings
|
||||
vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring
|
||||
vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes
|
||||
|
||||
vtkSmartPointer<::vtkPlane> m_ClipPlane;
|
||||
|
||||
vtkSmartPointer<::vtkCellPicker> m_Picker;
|
||||
vtkSmartPointer<::vtkTransform> m_InitialTransform;
|
||||
|
||||
std::vector<vtkSmartPointer<::vtkTransform>> m_TransformChain;
|
||||
vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix;
|
||||
|
||||
HandlerWidgetData() {
|
||||
m_Picker = vtkSmartPointer<::vtkCellPicker>::New();
|
||||
m_InitialTransform = vtkSmartPointer<::vtkTransform>::New();
|
||||
m_ClipPlane = vtkSmartPointer<::vtkPlane>::New();
|
||||
m_OverlayRenderer = vtkSmartPointer<::vtkRenderer>::New();
|
||||
m_BaseMatrix = vtkSmartPointer<::vtkMatrix4x4>::New();
|
||||
m_HighlightedProp = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
vtkStandardNewMacro(vtkHandlerWidget);
|
||||
|
||||
vtkHandlerWidget::vtkHandlerWidget() {
|
||||
vtkHandlerWidget::vtkHandlerWidget() : d(new HandlerWidgetData()) {
|
||||
this->Interaction = IDLE;
|
||||
this->m_Picker = vtkSmartPointer<::vtkCellPicker>::New();
|
||||
this->m_Picker->SetTolerance(0.01); // Increased tolerance for thin gizmos
|
||||
this->m_InitialTransform = vtkSmartPointer<::vtkTransform>::New();
|
||||
d->m_Picker->SetTolerance(0.01); // Increased tolerance for thin gizmos
|
||||
this->EventCallbackCommand->SetCallback(vtkHandlerWidget::ProcessEvents);
|
||||
this->EventCallbackCommand->SetClientData(this);
|
||||
this->m_Frame = LOCAL;
|
||||
this->m_HighlightedProp = nullptr;
|
||||
this->m_ClipPlane = vtkSmartPointer<::vtkPlane>::New();
|
||||
this->m_OverlayRenderer = vtkSmartPointer<::vtkRenderer>::New();
|
||||
this->m_OverlayRenderer->SetLayer(1);
|
||||
this->m_OverlayRenderer->EraseOff();
|
||||
this->m_OverlayRenderer->InteractiveOff();
|
||||
d->m_OverlayRenderer->SetLayer(1);
|
||||
d->m_OverlayRenderer->EraseOff();
|
||||
d->m_OverlayRenderer->InteractiveOff();
|
||||
this->Priority = 50.0; // Higher priority to beat camera style
|
||||
this->m_TranslationEnabled = true;
|
||||
this->m_RotationEnabled = true;
|
||||
this->m_ScalingEnabled = true;
|
||||
this->m_BaseMatrix = vtkSmartPointer<::vtkMatrix4x4>::New();
|
||||
this->m_BaseMatrix->Identity();
|
||||
d->m_BaseMatrix->Identity();
|
||||
this->CreateGizmos();
|
||||
}
|
||||
|
||||
vtkHandlerWidget::~vtkHandlerWidget() {}
|
||||
vtkHandlerWidget::~vtkHandlerWidget() {
|
||||
this->SetEnabled(0);
|
||||
delete d;
|
||||
}
|
||||
|
||||
::vtkRenderer *vtkHandlerWidget::GetOverlayRenderer() {
|
||||
return d->m_OverlayRenderer;
|
||||
}
|
||||
|
||||
void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) {
|
||||
if (this->Prop3D == prop) {
|
||||
@@ -84,13 +114,13 @@ void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) {
|
||||
}
|
||||
this->Prop3D = prop;
|
||||
if (this->Prop3D) {
|
||||
// Initialize m_BaseMatrix from the object's current matrix
|
||||
// Initialize d->m_BaseMatrix from the object's current matrix
|
||||
if (this->Prop3D->GetUserMatrix()) {
|
||||
this->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix());
|
||||
this->d->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix());
|
||||
} else {
|
||||
this->m_BaseMatrix->Identity();
|
||||
this->d->m_BaseMatrix->Identity();
|
||||
}
|
||||
this->m_TransformChain.clear(); // Clear any previous transform chain
|
||||
this->d->m_TransformChain.clear(); // Clear any previous transform chain
|
||||
this->UpdateGizmoPosition();
|
||||
}
|
||||
this->Modified();
|
||||
@@ -141,20 +171,20 @@ void vtkHandlerWidget::SetEnabled(int enabling) {
|
||||
}
|
||||
|
||||
// Sync Viewport and Camera
|
||||
this->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
||||
this->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
||||
win->AddRenderer(this->m_OverlayRenderer);
|
||||
this->d->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
||||
this->d->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
||||
win->AddRenderer(this->d->m_OverlayRenderer);
|
||||
|
||||
this->m_OverlayRenderer->AddActor(m_AxesX);
|
||||
this->m_OverlayRenderer->AddActor(m_AxesY);
|
||||
this->m_OverlayRenderer->AddActor(m_AxesZ);
|
||||
this->m_OverlayRenderer->AddActor(m_RotX);
|
||||
this->m_OverlayRenderer->AddActor(m_RotY);
|
||||
this->m_OverlayRenderer->AddActor(m_RotZ);
|
||||
this->m_OverlayRenderer->AddActor(m_RotCam);
|
||||
this->m_OverlayRenderer->AddActor(m_ScaleX);
|
||||
this->m_OverlayRenderer->AddActor(m_ScaleY);
|
||||
this->m_OverlayRenderer->AddActor(m_ScaleZ);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_AxesX);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_AxesY);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_AxesZ);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_RotX);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_RotY);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_RotZ);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_RotCam);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_ScaleX);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_ScaleY);
|
||||
this->d->m_OverlayRenderer->AddActor(d->m_ScaleZ);
|
||||
|
||||
this->UpdateVisibility();
|
||||
|
||||
@@ -165,11 +195,14 @@ void vtkHandlerWidget::SetEnabled(int enabling) {
|
||||
|
||||
this->Enabled = 0;
|
||||
this->Highlight(nullptr);
|
||||
this->Interactor->RemoveObserver(this->EventCallbackCommand);
|
||||
if (this->Interactor->GetRenderWindow()) {
|
||||
this->Interactor->GetRenderWindow()->RemoveRenderer(this->m_OverlayRenderer);
|
||||
if (this->Interactor) {
|
||||
this->Interactor->RemoveObserver(this->EventCallbackCommand);
|
||||
if (this->Interactor->GetRenderWindow()) {
|
||||
this->Interactor->GetRenderWindow()->MakeCurrent();
|
||||
this->Interactor->GetRenderWindow()->RemoveRenderer(this->d->m_OverlayRenderer);
|
||||
}
|
||||
}
|
||||
this->m_OverlayRenderer->RemoveAllViewProps();
|
||||
this->d->m_OverlayRenderer->RemoveAllViewProps();
|
||||
this->InvokeEvent(::vtkCommand::DisableEvent, nullptr);
|
||||
}
|
||||
|
||||
@@ -214,15 +247,15 @@ void vtkHandlerWidget::OnKeyPress() {
|
||||
bool ctrl = (this->Interactor->GetControlKey() != 0);
|
||||
|
||||
if (ctrl && key == "z") {
|
||||
if (!this->m_TransformChain.empty()) {
|
||||
if (!this->d->m_TransformChain.empty()) {
|
||||
std::cout << "Undoing last transform action..." << std::endl;
|
||||
this->m_TransformChain.pop_back();
|
||||
this->d->m_TransformChain.pop_back();
|
||||
|
||||
// Update object from chain
|
||||
vtkNew<vtkTransform> total;
|
||||
total->PostMultiply();
|
||||
total->SetMatrix(this->m_BaseMatrix.GetPointer());
|
||||
for (auto& t : m_TransformChain) {
|
||||
total->SetMatrix(this->d->m_BaseMatrix.GetPointer());
|
||||
for (auto& t : d->m_TransformChain) {
|
||||
total->Concatenate(t);
|
||||
}
|
||||
|
||||
@@ -245,33 +278,33 @@ void vtkHandlerWidget::OnLeftButtonDown() {
|
||||
this->CurrentRenderer = this->Interactor->FindPokedRenderer(X, Y);
|
||||
}
|
||||
|
||||
this->m_Picker->Pick(X, Y, 0.0, this->m_OverlayRenderer);
|
||||
::vtkProp *prop = this->m_Picker->GetViewProp();
|
||||
this->m_Picker->GetPickPosition(this->m_StartPickPosition);
|
||||
this->d->m_Picker->Pick(X, Y, 0.0, this->d->m_OverlayRenderer);
|
||||
::vtkProp *prop = this->d->m_Picker->GetViewProp();
|
||||
this->d->m_Picker->GetPickPosition(this->m_StartPickPosition);
|
||||
|
||||
if (!prop)
|
||||
return;
|
||||
|
||||
this->Interaction = IDLE;
|
||||
if (prop == m_AxesX)
|
||||
if (prop == d->m_AxesX)
|
||||
this->Interaction = TRANS_X;
|
||||
else if (prop == m_AxesY)
|
||||
else if (prop == d->m_AxesY)
|
||||
this->Interaction = TRANS_Y;
|
||||
else if (prop == m_AxesZ)
|
||||
else if (prop == d->m_AxesZ)
|
||||
this->Interaction = TRANS_Z;
|
||||
else if (prop == m_RotX)
|
||||
else if (prop == d->m_RotX)
|
||||
this->Interaction = ROT_X;
|
||||
else if (prop == m_RotY)
|
||||
else if (prop == d->m_RotY)
|
||||
this->Interaction = ROT_Y;
|
||||
else if (prop == m_RotZ)
|
||||
else if (prop == d->m_RotZ)
|
||||
this->Interaction = ROT_Z;
|
||||
else if (prop == m_ScaleX)
|
||||
else if (prop == d->m_ScaleX)
|
||||
this->Interaction = SCALE_X;
|
||||
else if (prop == m_ScaleY)
|
||||
else if (prop == d->m_ScaleY)
|
||||
this->Interaction = SCALE_Y;
|
||||
else if (prop == m_ScaleZ)
|
||||
else if (prop == d->m_ScaleZ)
|
||||
this->Interaction = SCALE_Z;
|
||||
else if (prop == m_RotCam)
|
||||
else if (prop == d->m_RotCam)
|
||||
this->Interaction = ROT_CAM;
|
||||
|
||||
if (this->Interaction != IDLE) {
|
||||
@@ -285,14 +318,14 @@ void vtkHandlerWidget::OnLeftButtonDown() {
|
||||
|
||||
// If the chain is empty, initialize base from current state?
|
||||
// Actually, if we just started selecting this object, we should have initialized BaseMatrix.
|
||||
// For now, let's keep m_InitialTransform as the state BEFORE this drag
|
||||
// For now, let's keep d->m_InitialTransform as the state BEFORE this drag
|
||||
vtkNew<vtkTransform> current;
|
||||
current->PostMultiply();
|
||||
current->SetMatrix(this->m_BaseMatrix.GetPointer());
|
||||
for (auto& t : m_TransformChain) {
|
||||
current->SetMatrix(this->d->m_BaseMatrix.GetPointer());
|
||||
for (auto& t : d->m_TransformChain) {
|
||||
current->Concatenate(t);
|
||||
}
|
||||
this->m_InitialTransform->SetMatrix(current->GetMatrix());
|
||||
this->d->m_InitialTransform->SetMatrix(current->GetMatrix());
|
||||
}
|
||||
this->EventCallbackCommand->SetAbortFlag(1);
|
||||
this->InvokeEvent(::vtkCommand::StartInteractionEvent, nullptr);
|
||||
@@ -310,10 +343,10 @@ void vtkHandlerWidget::OnLeftButtonUp() {
|
||||
|
||||
// We need to re-calculate the final 'op' to store it
|
||||
// Actually, we could have stored it in OnMouseMove, but let's re-calculate or
|
||||
// just capture the delta between m_InitialTransform and current UserMatrix.
|
||||
// just capture the delta between d->m_InitialTransform and current UserMatrix.
|
||||
if (this->Prop3D && this->Prop3D->GetUserMatrix()) {
|
||||
vtkNew<vtkMatrix4x4> inv;
|
||||
vtkMatrix4x4::Invert(this->m_InitialTransform->GetMatrix(), inv);
|
||||
vtkMatrix4x4::Invert(this->d->m_InitialTransform->GetMatrix(), inv);
|
||||
|
||||
vtkNew<vtkMatrix4x4> final_op_mat;
|
||||
vtkMatrix4x4::Multiply4x4(this->Prop3D->GetUserMatrix(), inv, final_op_mat);
|
||||
@@ -321,8 +354,8 @@ void vtkHandlerWidget::OnLeftButtonUp() {
|
||||
vtkNew<vtkTransform> final_op;
|
||||
final_op->SetMatrix(final_op_mat);
|
||||
|
||||
this->m_TransformChain.push_back(final_op);
|
||||
std::cout << "Action finalized. Chain size: " << this->m_TransformChain.size() << std::endl;
|
||||
this->d->m_TransformChain.push_back(final_op);
|
||||
std::cout << "Action finalized. Chain size: " << this->d->m_TransformChain.size() << std::endl;
|
||||
}
|
||||
|
||||
this->Interaction = IDLE;
|
||||
@@ -339,8 +372,8 @@ void vtkHandlerWidget::OnMouseMove() {
|
||||
int Y = this->Interactor->GetEventPosition()[1];
|
||||
|
||||
if (this->Interaction == IDLE) {
|
||||
this->m_Picker->Pick(X, Y, 0.0, this->m_OverlayRenderer);
|
||||
::vtkProp *prop = this->m_Picker->GetViewProp();
|
||||
this->d->m_Picker->Pick(X, Y, 0.0, this->d->m_OverlayRenderer);
|
||||
::vtkProp *prop = this->d->m_Picker->GetViewProp();
|
||||
this->Highlight(prop);
|
||||
this->UpdateGizmoPosition(); // Ensure camera adjustments happen
|
||||
return;
|
||||
@@ -353,7 +386,7 @@ void vtkHandlerWidget::OnMouseMove() {
|
||||
// std::cout << "Interaction " << this->Interaction << " dx=" << dx << " dy=" << dy << std::endl;
|
||||
|
||||
// Get current gizmo properties from its actors
|
||||
vtkMatrix4x4 *gizmo_mat = m_AxesX->GetUserMatrix();
|
||||
vtkMatrix4x4 *gizmo_mat = d->m_AxesX->GetUserMatrix();
|
||||
if (!gizmo_mat)
|
||||
return;
|
||||
|
||||
@@ -542,7 +575,7 @@ void vtkHandlerWidget::OnMouseMove() {
|
||||
// Total transform = Base * Chain * Interaction
|
||||
vtkNew<vtkTransform> total;
|
||||
total->PostMultiply();
|
||||
total->SetMatrix(this->m_InitialTransform->GetMatrix()); // m_InitialTransform is already Base*Chain
|
||||
total->SetMatrix(this->d->m_InitialTransform->GetMatrix()); // d->m_InitialTransform is already Base*Chain
|
||||
total->Concatenate(op);
|
||||
|
||||
vtkMatrix4x4* targetMat = this->Prop3D->GetUserMatrix();
|
||||
@@ -588,41 +621,41 @@ void vtkHandlerWidget::SetScalingEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void vtkHandlerWidget::UpdateVisibility() {
|
||||
if (!m_AxesX) return;
|
||||
if (!d->m_AxesX) return;
|
||||
|
||||
m_AxesX->SetVisibility(m_TranslationEnabled);
|
||||
m_AxesY->SetVisibility(m_TranslationEnabled);
|
||||
m_AxesZ->SetVisibility(m_TranslationEnabled);
|
||||
d->m_AxesX->SetVisibility(m_TranslationEnabled);
|
||||
d->m_AxesY->SetVisibility(m_TranslationEnabled);
|
||||
d->m_AxesZ->SetVisibility(m_TranslationEnabled);
|
||||
|
||||
m_RotX->SetVisibility(m_RotationEnabled);
|
||||
m_RotY->SetVisibility(m_RotationEnabled);
|
||||
m_RotZ->SetVisibility(m_RotationEnabled);
|
||||
m_RotCam->SetVisibility(m_RotationEnabled);
|
||||
d->m_RotX->SetVisibility(m_RotationEnabled);
|
||||
d->m_RotY->SetVisibility(m_RotationEnabled);
|
||||
d->m_RotZ->SetVisibility(m_RotationEnabled);
|
||||
d->m_RotCam->SetVisibility(m_RotationEnabled);
|
||||
|
||||
m_ScaleX->SetVisibility(m_ScalingEnabled);
|
||||
m_ScaleY->SetVisibility(m_ScalingEnabled);
|
||||
m_ScaleZ->SetVisibility(m_ScalingEnabled);
|
||||
d->m_ScaleX->SetVisibility(m_ScalingEnabled);
|
||||
d->m_ScaleY->SetVisibility(m_ScalingEnabled);
|
||||
d->m_ScaleZ->SetVisibility(m_ScalingEnabled);
|
||||
|
||||
// Update picker list
|
||||
if (m_Picker) {
|
||||
m_Picker->InitializePickList();
|
||||
if (d->m_Picker) {
|
||||
d->m_Picker->InitializePickList();
|
||||
if (m_TranslationEnabled) {
|
||||
m_Picker->AddPickList(m_AxesX);
|
||||
m_Picker->AddPickList(m_AxesY);
|
||||
m_Picker->AddPickList(m_AxesZ);
|
||||
d->m_Picker->AddPickList(d->m_AxesX);
|
||||
d->m_Picker->AddPickList(d->m_AxesY);
|
||||
d->m_Picker->AddPickList(d->m_AxesZ);
|
||||
}
|
||||
if (m_RotationEnabled) {
|
||||
m_Picker->AddPickList(m_RotX);
|
||||
m_Picker->AddPickList(m_RotY);
|
||||
m_Picker->AddPickList(m_RotZ);
|
||||
m_Picker->AddPickList(m_RotCam);
|
||||
d->m_Picker->AddPickList(d->m_RotX);
|
||||
d->m_Picker->AddPickList(d->m_RotY);
|
||||
d->m_Picker->AddPickList(d->m_RotZ);
|
||||
d->m_Picker->AddPickList(d->m_RotCam);
|
||||
}
|
||||
if (m_ScalingEnabled) {
|
||||
m_Picker->AddPickList(m_ScaleX);
|
||||
m_Picker->AddPickList(m_ScaleY);
|
||||
m_Picker->AddPickList(m_ScaleZ);
|
||||
d->m_Picker->AddPickList(d->m_ScaleX);
|
||||
d->m_Picker->AddPickList(d->m_ScaleY);
|
||||
d->m_Picker->AddPickList(d->m_ScaleZ);
|
||||
}
|
||||
m_Picker->PickFromListOn();
|
||||
d->m_Picker->PickFromListOn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,7 +709,7 @@ void vtkHandlerWidget::CreateGizmos() {
|
||||
|
||||
auto mapper = vtkSmartPointer<::vtkPolyDataMapper>::New();
|
||||
mapper->SetInputConnection(circle->GetOutputPort());
|
||||
mapper->AddClippingPlane(this->m_ClipPlane);
|
||||
mapper->AddClippingPlane(this->d->m_ClipPlane);
|
||||
|
||||
auto actor = vtkSmartPointer<::vtkActor>::New();
|
||||
actor->SetMapper(mapper);
|
||||
@@ -690,15 +723,15 @@ void vtkHandlerWidget::CreateGizmos() {
|
||||
blue[] = {0.0, 0.0, 1.0}, white[] = {1.0, 1.0, 1.0};
|
||||
|
||||
double x[] = {1, 0, 0}, y[] = {0, 1, 0}, z[] = {0, 0, 1};
|
||||
m_AxesX = create_arrow(x, red);
|
||||
m_AxesY = create_arrow(y, green);
|
||||
m_AxesZ = create_arrow(z, blue);
|
||||
d->m_AxesX = create_arrow(x, red);
|
||||
d->m_AxesY = create_arrow(y, green);
|
||||
d->m_AxesZ = create_arrow(z, blue);
|
||||
|
||||
m_RotX = create_ring(0, red);
|
||||
m_RotY = create_ring(1, green);
|
||||
m_RotZ = create_ring(2, blue);
|
||||
d->m_RotX = create_ring(0, red);
|
||||
d->m_RotY = create_ring(1, green);
|
||||
d->m_RotZ = create_ring(2, blue);
|
||||
|
||||
m_RotCam = vtkSmartPointer<::vtkActor>::New();
|
||||
d->m_RotCam = vtkSmartPointer<::vtkActor>::New();
|
||||
{
|
||||
auto circle = vtkSmartPointer<::vtkRegularPolygonSource>::New();
|
||||
circle->SetNumberOfSides(64);
|
||||
@@ -708,10 +741,10 @@ void vtkHandlerWidget::CreateGizmos() {
|
||||
circle->GeneratePolylineOn();
|
||||
auto mapper = vtkSmartPointer<::vtkPolyDataMapper>::New();
|
||||
mapper->SetInputConnection(circle->GetOutputPort());
|
||||
m_RotCam->SetMapper(mapper);
|
||||
m_RotCam->GetProperty()->SetColor(white);
|
||||
m_RotCam->GetProperty()->SetLineWidth(2);
|
||||
m_RotCam->GetProperty()->SetLighting(0);
|
||||
d->m_RotCam->SetMapper(mapper);
|
||||
d->m_RotCam->GetProperty()->SetColor(white);
|
||||
d->m_RotCam->GetProperty()->SetLineWidth(2);
|
||||
d->m_RotCam->GetProperty()->SetLighting(0);
|
||||
}
|
||||
|
||||
auto create_cube = [](double pos[3], double color[3]) {
|
||||
@@ -730,23 +763,23 @@ void vtkHandlerWidget::CreateGizmos() {
|
||||
};
|
||||
|
||||
double px[] = {1.2, 0, 0}, py[] = {0, 1.2, 0}, pz[] = {0, 0, 1.2};
|
||||
m_ScaleX = create_cube(px, red);
|
||||
m_ScaleY = create_cube(py, green);
|
||||
m_ScaleZ = create_cube(pz, blue);
|
||||
d->m_ScaleX = create_cube(px, red);
|
||||
d->m_ScaleY = create_cube(py, green);
|
||||
d->m_ScaleZ = create_cube(pz, blue);
|
||||
|
||||
// Configure picker to only see gizmo actors (Pick-Through)
|
||||
m_Picker->InitializePickList();
|
||||
m_Picker->AddPickList(m_AxesX);
|
||||
m_Picker->AddPickList(m_AxesY);
|
||||
m_Picker->AddPickList(m_AxesZ);
|
||||
m_Picker->AddPickList(m_RotX);
|
||||
m_Picker->AddPickList(m_RotY);
|
||||
m_Picker->AddPickList(m_RotZ);
|
||||
m_Picker->AddPickList(m_RotCam);
|
||||
m_Picker->AddPickList(m_ScaleX);
|
||||
m_Picker->AddPickList(m_ScaleY);
|
||||
m_Picker->AddPickList(m_ScaleZ);
|
||||
m_Picker->PickFromListOn();
|
||||
d->m_Picker->InitializePickList();
|
||||
d->m_Picker->AddPickList(d->m_AxesX);
|
||||
d->m_Picker->AddPickList(d->m_AxesY);
|
||||
d->m_Picker->AddPickList(d->m_AxesZ);
|
||||
d->m_Picker->AddPickList(d->m_RotX);
|
||||
d->m_Picker->AddPickList(d->m_RotY);
|
||||
d->m_Picker->AddPickList(d->m_RotZ);
|
||||
d->m_Picker->AddPickList(d->m_RotCam);
|
||||
d->m_Picker->AddPickList(d->m_ScaleX);
|
||||
d->m_Picker->AddPickList(d->m_ScaleY);
|
||||
d->m_Picker->AddPickList(d->m_ScaleZ);
|
||||
d->m_Picker->PickFromListOn();
|
||||
}
|
||||
|
||||
void vtkHandlerWidget::UpdateGizmoPosition() {
|
||||
@@ -763,21 +796,23 @@ void vtkHandlerWidget::UpdateGizmoPosition() {
|
||||
if (bboxSize < 1e-6) bboxSize = 1.0;
|
||||
|
||||
double screenLimit = bboxSize * 2.0; // Default if no renderer
|
||||
if (this->CurrentRenderer) {
|
||||
int *sz = this->CurrentRenderer->GetSize();
|
||||
if (sz[1] > 0) {
|
||||
double pixelSize = std::min(sz[0], sz[1]) / 5.0;
|
||||
vtkCamera *cam = this->CurrentRenderer->GetActiveCamera();
|
||||
if (cam->GetParallelProjection()) {
|
||||
screenLimit = (pixelSize / (double)sz[1]) * 2.0 * cam->GetParallelScale();
|
||||
} else {
|
||||
double dist = cam->GetDistance();
|
||||
double angleRad = vtkMath::Pi() * cam->GetViewAngle() / 180.0;
|
||||
double viewHeightAtDist = 2.0 * dist * tan(angleRad / 2.0);
|
||||
screenLimit = (pixelSize / (double)sz[1]) * viewHeightAtDist;
|
||||
if (this->CurrentRenderer && this->CurrentRenderer->GetRenderWindow() && this->CurrentRenderer->GetRenderWindow()->GetInteractor()) {
|
||||
int *sz = this->CurrentRenderer->GetSize();
|
||||
if (sz[1] > 0) {
|
||||
double pixelSize = std::min(sz[0], sz[1]) / 5.0;
|
||||
vtkCamera *cam = this->CurrentRenderer->GetActiveCamera();
|
||||
if (cam) {
|
||||
if (cam->GetParallelProjection()) {
|
||||
screenLimit = (pixelSize / (double)sz[1]) * 2.0 * cam->GetParallelScale();
|
||||
} else {
|
||||
double dist = cam->GetDistance();
|
||||
double angleRad = vtkMath::Pi() * cam->GetViewAngle() / 180.0;
|
||||
double viewHeightAtDist = 2.0 * dist * tan(angleRad / 2.0);
|
||||
screenLimit = (pixelSize / (double)sz[1]) * viewHeightAtDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
double scaleFactor = std::min(bboxSize, screenLimit);
|
||||
|
||||
vtkNew<vtkMatrix4x4> mat_gizmo;
|
||||
@@ -843,24 +878,24 @@ void vtkHandlerWidget::UpdateGizmoPosition() {
|
||||
}
|
||||
}
|
||||
|
||||
m_AxesX->SetUserMatrix(mat_gizmo);
|
||||
m_AxesY->SetUserMatrix(mat_gizmo);
|
||||
m_AxesZ->SetUserMatrix(mat_gizmo);
|
||||
m_RotX->SetUserMatrix(mat_gizmo);
|
||||
m_RotY->SetUserMatrix(mat_gizmo);
|
||||
m_RotZ->SetUserMatrix(mat_gizmo);
|
||||
m_ScaleX->SetUserMatrix(mat_gizmo);
|
||||
m_ScaleY->SetUserMatrix(mat_gizmo);
|
||||
m_ScaleZ->SetUserMatrix(mat_gizmo);
|
||||
d->m_AxesX->SetUserMatrix(mat_gizmo);
|
||||
d->m_AxesY->SetUserMatrix(mat_gizmo);
|
||||
d->m_AxesZ->SetUserMatrix(mat_gizmo);
|
||||
d->m_RotX->SetUserMatrix(mat_gizmo);
|
||||
d->m_RotY->SetUserMatrix(mat_gizmo);
|
||||
d->m_RotZ->SetUserMatrix(mat_gizmo);
|
||||
d->m_ScaleX->SetUserMatrix(mat_gizmo);
|
||||
d->m_ScaleY->SetUserMatrix(mat_gizmo);
|
||||
d->m_ScaleZ->SetUserMatrix(mat_gizmo);
|
||||
|
||||
// Sync Overlay Renderer with Main Renderer
|
||||
if (this->CurrentRenderer && this->m_OverlayRenderer) {
|
||||
this->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
||||
this->m_OverlayRenderer->SetAspect(this->CurrentRenderer->GetAspect());
|
||||
this->m_OverlayRenderer->ComputeAspect();
|
||||
if (this->CurrentRenderer && this->d->m_OverlayRenderer) {
|
||||
this->d->m_OverlayRenderer->SetViewport(this->CurrentRenderer->GetViewport());
|
||||
this->d->m_OverlayRenderer->SetAspect(this->CurrentRenderer->GetAspect());
|
||||
this->d->m_OverlayRenderer->ComputeAspect();
|
||||
|
||||
if (this->m_OverlayRenderer->GetActiveCamera() != this->CurrentRenderer->GetActiveCamera()) {
|
||||
this->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
||||
if (this->d->m_OverlayRenderer->GetActiveCamera() != this->CurrentRenderer->GetActiveCamera()) {
|
||||
this->d->m_OverlayRenderer->SetActiveCamera(this->CurrentRenderer->GetActiveCamera());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,36 +925,36 @@ void vtkHandlerWidget::UpdateGizmoPosition() {
|
||||
}
|
||||
tcam->Translate(mat_gizmo->GetElement(0, 3), mat_gizmo->GetElement(1, 3),
|
||||
mat_gizmo->GetElement(2, 3));
|
||||
m_RotCam->SetUserMatrix(tcam->GetMatrix());
|
||||
d->m_RotCam->SetUserMatrix(tcam->GetMatrix());
|
||||
|
||||
// Update clipping plane for axes rings
|
||||
this->m_ClipPlane->SetOrigin(mat_gizmo->GetElement(0, 3),
|
||||
this->d->m_ClipPlane->SetOrigin(mat_gizmo->GetElement(0, 3),
|
||||
mat_gizmo->GetElement(1, 3),
|
||||
mat_gizmo->GetElement(2, 3));
|
||||
this->m_ClipPlane->SetNormal(dir);
|
||||
this->d->m_ClipPlane->SetNormal(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void vtkHandlerWidget::Highlight(::vtkProp *prop) {
|
||||
if (this->m_HighlightedProp == prop)
|
||||
if (this->d->m_HighlightedProp == prop)
|
||||
return;
|
||||
|
||||
// Restore previous
|
||||
if (this->m_HighlightedProp) {
|
||||
::vtkActor *actor = ::vtkActor::SafeDownCast(this->m_HighlightedProp);
|
||||
if (this->d->m_HighlightedProp) {
|
||||
::vtkActor *actor = ::vtkActor::SafeDownCast(this->d->m_HighlightedProp);
|
||||
if (actor) {
|
||||
actor->GetProperty()->SetColor(m_OriginalColor);
|
||||
actor->GetProperty()->SetLineWidth(3);
|
||||
}
|
||||
}
|
||||
|
||||
this->m_HighlightedProp = nullptr;
|
||||
this->d->m_HighlightedProp = nullptr;
|
||||
|
||||
// Highlight new if it belongs to us
|
||||
if (prop == m_AxesX || prop == m_AxesY || prop == m_AxesZ || prop == m_RotX ||
|
||||
prop == m_RotY || prop == m_RotZ || prop == m_RotCam || prop == m_ScaleX ||
|
||||
prop == m_ScaleY || prop == m_ScaleZ) {
|
||||
this->m_HighlightedProp = prop;
|
||||
if (prop == d->m_AxesX || prop == d->m_AxesY || prop == d->m_AxesZ || prop == d->m_RotX ||
|
||||
prop == d->m_RotY || prop == d->m_RotZ || prop == d->m_RotCam || prop == d->m_ScaleX ||
|
||||
prop == d->m_ScaleY || prop == d->m_ScaleZ) {
|
||||
this->d->m_HighlightedProp = prop;
|
||||
::vtkActor *actor = ::vtkActor::SafeDownCast(prop);
|
||||
if (actor) {
|
||||
actor->GetProperty()->GetColor(m_OriginalColor);
|
||||
|
||||
@@ -92,6 +92,8 @@ public:
|
||||
|
||||
void SetReferenceFrame(ReferenceFrame frame);
|
||||
ReferenceFrame GetReferenceFrame() const { return this->m_Frame; }
|
||||
|
||||
struct HandlerWidgetData *d;
|
||||
|
||||
using ::vtk3DWidget::PlaceWidget;
|
||||
virtual void PlaceWidget(double bounds[6]) override;
|
||||
@@ -101,7 +103,7 @@ public:
|
||||
void SetTransform(::vtkTransform *t);
|
||||
void GetTransform(::vtkTransform *t);
|
||||
|
||||
::vtkRenderer *GetOverlayRenderer() { return this->m_OverlayRenderer; }
|
||||
::vtkRenderer *GetOverlayRenderer();
|
||||
|
||||
void SetTranslationEnabled(bool enabled);
|
||||
bool GetTranslationEnabled() const { return m_TranslationEnabled; }
|
||||
@@ -109,39 +111,23 @@ public:
|
||||
bool GetRotationEnabled() const { return m_RotationEnabled; }
|
||||
void SetScalingEnabled(bool enabled);
|
||||
bool GetScalingEnabled() const { return m_ScalingEnabled; }
|
||||
|
||||
|
||||
protected:
|
||||
void CreateGizmos();
|
||||
void UpdateGizmoPosition();
|
||||
void Highlight(::vtkProp *prop);
|
||||
void UpdateVisibility();
|
||||
|
||||
vtkSmartPointer<::vtkRenderer> m_OverlayRenderer;
|
||||
|
||||
ReferenceFrame m_Frame;
|
||||
bool m_TranslationEnabled;
|
||||
bool m_RotationEnabled;
|
||||
bool m_ScalingEnabled;
|
||||
|
||||
|
||||
int Interaction;
|
||||
::vtkProp *m_HighlightedProp;
|
||||
double m_OriginalColor[3];
|
||||
|
||||
// Visual components //
|
||||
vtkSmartPointer<::vtkActor> m_AxesX, m_AxesY, m_AxesZ; // Arrows
|
||||
vtkSmartPointer<::vtkActor> m_RotX, m_RotY, m_RotZ; // Rings
|
||||
vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring
|
||||
vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes
|
||||
|
||||
vtkSmartPointer<::vtkPlane> m_ClipPlane;
|
||||
|
||||
vtkSmartPointer<::vtkCellPicker> m_Picker;
|
||||
|
||||
double StartEventPosition[2];
|
||||
double m_StartPickPosition[3];
|
||||
vtkSmartPointer<::vtkTransform> m_InitialTransform;
|
||||
|
||||
std::vector<vtkSmartPointer<::vtkTransform>> m_TransformChain;
|
||||
vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix;
|
||||
|
||||
public:
|
||||
virtual void SetProp3D(::vtkProp3D *prop) override;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "vtkObjectsContext.h"
|
||||
#include "Vtk/Math/vtkContainerBox.h"
|
||||
#include "Vtk/Math/vtkCylinder.h"
|
||||
#include "Vtk/Math/vtkAssembly.h"
|
||||
#include "Vtk/Math/vtkVoxImage.h"
|
||||
#include "HEP/Detectors/vtkDetectorChamber.h"
|
||||
|
||||
#include <vtkAssembly.h>
|
||||
@@ -42,12 +44,8 @@ void vtkObjectsContext::Synchronize() {
|
||||
it->second->DisconnectRenderer(nullptr); // If we have a ref to a renderer we should disconnect but Puppet doesn't store it easily
|
||||
// Actually Puppet::DisconnectRenderer(vtkRenderer*) needs the renderer.
|
||||
// For now we just remove from assembly
|
||||
vtkPropCollection* props = it->second->GetProps();
|
||||
props->InitTraversal();
|
||||
while(vtkProp* prop = props->GetNextProp()) {
|
||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
||||
m_Assembly->RemovePart(p3d);
|
||||
}
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(it->second->GetProp()))
|
||||
m_Assembly->RemovePart(p3d);
|
||||
this->PuppetRemoved(it->second);
|
||||
delete it->second;
|
||||
it = m_Puppets.erase(it);
|
||||
@@ -62,12 +60,8 @@ void vtkObjectsContext::Synchronize() {
|
||||
Puppet* puppet = this->CreatePuppet(obj);
|
||||
if (puppet) {
|
||||
m_Puppets[obj] = puppet;
|
||||
vtkPropCollection* props = puppet->GetProps();
|
||||
props->InitTraversal();
|
||||
while(vtkProp* prop = props->GetNextProp()) {
|
||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
||||
m_Assembly->AddPart(p3d);
|
||||
}
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(puppet->GetProp()))
|
||||
m_Assembly->AddPart(p3d);
|
||||
this->PuppetAdded(puppet);
|
||||
}
|
||||
}
|
||||
@@ -80,12 +74,8 @@ void vtkObjectsContext::OnObjectAdded(uLib::Object* obj) {
|
||||
Puppet* puppet = this->CreatePuppet(obj);
|
||||
if (puppet) {
|
||||
m_Puppets[obj] = puppet;
|
||||
vtkPropCollection* props = puppet->GetProps();
|
||||
props->InitTraversal();
|
||||
while(vtkProp* prop = props->GetNextProp()) {
|
||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
||||
m_Assembly->AddPart(p3d);
|
||||
}
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(puppet->GetProp()))
|
||||
m_Assembly->AddPart(p3d);
|
||||
this->PuppetAdded(puppet);
|
||||
}
|
||||
}
|
||||
@@ -97,12 +87,8 @@ void vtkObjectsContext::OnObjectRemoved(uLib::Object* obj) {
|
||||
if (it != m_Puppets.end()) {
|
||||
// For now we just remove from assembly.
|
||||
// Puppet::DisconnectRenderer(vtkRenderer*) needs the renderer, but we don't have it here easily.
|
||||
vtkPropCollection* props = it->second->GetProps();
|
||||
props->InitTraversal();
|
||||
while(vtkProp* prop = props->GetNextProp()) {
|
||||
if (vtkProp3D* p3d = vtkProp3D::SafeDownCast(prop))
|
||||
m_Assembly->RemovePart(p3d);
|
||||
}
|
||||
if (auto* p3d = vtkProp3D::SafeDownCast(it->second->GetProp()))
|
||||
m_Assembly->RemovePart(p3d);
|
||||
this->PuppetRemoved(it->second);
|
||||
delete it->second;
|
||||
m_Puppets.erase(it);
|
||||
@@ -131,6 +117,10 @@ Puppet* vtkObjectsContext::CreatePuppet(uLib::Object* obj) {
|
||||
return new vtkDetectorChamber(static_cast<uLib::DetectorChamber*>(obj));
|
||||
} else if (std::strcmp(className, "Cylinder") == 0) {
|
||||
return new vtkCylinder(static_cast<uLib::Cylinder*>(obj));
|
||||
} else if (std::strcmp(className, "VoxImage") == 0) {
|
||||
return new vtkVoxImage(*static_cast<uLib::Abstract::VoxImage*>(obj));
|
||||
} else if (std::strcmp(className, "Assembly") == 0) {
|
||||
return new Assembly(static_cast<uLib::Assembly*>(obj));
|
||||
}
|
||||
|
||||
// Fallback if we don't know the exact class but it might be a context itself
|
||||
|
||||
@@ -25,6 +25,8 @@ public:
|
||||
/** @brief Returns the puppet associated with a specific core object. */
|
||||
Puppet* GetPuppet(uLib::Object* obj);
|
||||
|
||||
const std::map<uLib::Object*, Puppet*>& GetPuppets() const { return m_Puppets; }
|
||||
|
||||
/** @brief Updates all managed puppets. */
|
||||
virtual void Update() override;
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ QViewport::~QViewport()
|
||||
void QViewport::SetupPipeline()
|
||||
{
|
||||
// Add our renderer to the QVTKOpenGLNativeWidget's render window
|
||||
vtkRenderWindow* rw = m_VtkWidget->renderWindow();
|
||||
rw->AddRenderer(m_Renderer);
|
||||
::vtkRenderWindow* rw = m_VtkWidget->renderWindow();
|
||||
rw->AddRenderer(this->GetRenderer());
|
||||
|
||||
// Common setup
|
||||
Viewport::SetupPipeline(rw->GetInteractor());
|
||||
@@ -101,6 +101,11 @@ void QViewport::onGridButtonClicked()
|
||||
SetGridVisible(m_GridButton->isChecked());
|
||||
}
|
||||
|
||||
void QViewport::OnSelectionChanged(Puppet* p)
|
||||
{
|
||||
emit puppetSelected(p);
|
||||
}
|
||||
|
||||
void QViewport::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Vtk {
|
||||
*/
|
||||
class QViewport : public QWidget, public Viewport {
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void puppetSelected(uLib::Vtk::Puppet* p);
|
||||
public:
|
||||
explicit QViewport(QWidget* parent = nullptr);
|
||||
virtual ~QViewport();
|
||||
@@ -42,6 +44,8 @@ public:
|
||||
virtual vtkRenderWindowInteractor* GetInteractor() override;
|
||||
QVTKOpenGLNativeWidget* GetWidget() { return m_VtkWidget; }
|
||||
|
||||
virtual void OnSelectionChanged(Puppet* p) override;
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
|
||||
@@ -6,42 +6,107 @@
|
||||
#include <vtkProp3DCollection.h>
|
||||
#include <vtkCamera.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vtkInteractorStyleTrackballCamera.h>
|
||||
#include <vtkObjectFactory.h>
|
||||
#include <vtkAxesActor.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
#include <vtkNew.h>
|
||||
#include <vtkCornerAnnotation.h>
|
||||
#include <vtkOrientationMarkerWidget.h>
|
||||
#include <vtkCameraOrientationWidget.h>
|
||||
#include <vtkPlaneSource.h>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkAxes.h>
|
||||
#include <vtkNamedColors.h>
|
||||
#include <vtkCellPicker.h>
|
||||
#include <vtkTextProperty.h>
|
||||
|
||||
#include <vtkPolyDataMapper.h>
|
||||
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkCallbackCommand.h>
|
||||
#include <vtkMath.h>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include "vtkHandlerWidget.h"
|
||||
#include "vtkObjectsContext.h"
|
||||
#include "Math/Assembly.h"
|
||||
#include "Math/ContainerBox.h"
|
||||
#include "Math/Cylinder.h"
|
||||
#include "Math/Transform.h"
|
||||
#include "Vtk/Math/vtkAssembly.h"
|
||||
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
struct ViewportData {
|
||||
vtkSmartPointer<vtkRenderer> m_Renderer;
|
||||
vtkSmartPointer<vtkCornerAnnotation> m_Annotation;
|
||||
vtkSmartPointer<vtkOrientationMarkerWidget> m_Marker;
|
||||
vtkSmartPointer<vtkCameraOrientationWidget> m_CameraWidget;
|
||||
|
||||
vtkSmartPointer<vtkPlaneSource> m_GridSource;
|
||||
vtkSmartPointer<vtkActor> m_GridActor;
|
||||
vtkSmartPointer<vtkAxes> m_OriginAxes;
|
||||
vtkSmartPointer<vtkActor> m_OriginAxesActor;
|
||||
|
||||
vtkSmartPointer<vtkNamedColors> m_Colors;
|
||||
|
||||
vtkSmartPointer<vtkHandlerWidget> m_HandlerWidget;
|
||||
vtkSmartPointer<vtkCellPicker> m_Picker;
|
||||
vtkSmartPointer<vtkCallbackCommand> m_KeyCallback;
|
||||
|
||||
ViewportData()
|
||||
: m_Renderer(vtkSmartPointer<vtkRenderer>::New())
|
||||
, m_Annotation(vtkSmartPointer<vtkCornerAnnotation>::New())
|
||||
, m_Marker(vtkSmartPointer<vtkOrientationMarkerWidget>::New())
|
||||
, m_CameraWidget(nullptr)
|
||||
, m_Colors(vtkSmartPointer<vtkNamedColors>::New())
|
||||
{}
|
||||
};
|
||||
|
||||
Viewport::Viewport()
|
||||
: m_Renderer(vtkSmartPointer<vtkRenderer>::New())
|
||||
, m_Annotation(vtkSmartPointer<vtkCornerAnnotation>::New())
|
||||
, m_Marker(vtkSmartPointer<vtkOrientationMarkerWidget>::New())
|
||||
, m_CameraWidget(nullptr)
|
||||
, m_Colors(vtkSmartPointer<vtkNamedColors>::New())
|
||||
: pv(new ViewportData())
|
||||
, m_GridAxis(Y)
|
||||
{
|
||||
}
|
||||
|
||||
Viewport::~Viewport()
|
||||
{
|
||||
if (m_Renderer) {
|
||||
m_Renderer->RemoveAllViewProps();
|
||||
void Viewport::DisableHandler() {
|
||||
if (pv->m_HandlerWidget) {
|
||||
pv->m_HandlerWidget->SetEnabled(0);
|
||||
}
|
||||
}
|
||||
|
||||
Viewport::~Viewport()
|
||||
{
|
||||
if (pv->m_HandlerWidget) {
|
||||
pv->m_HandlerWidget->SetEnabled(0);
|
||||
pv->m_HandlerWidget->SetInteractor(nullptr);
|
||||
pv->m_HandlerWidget = nullptr;
|
||||
}
|
||||
if (pv->m_Renderer) {
|
||||
if (pv->m_Renderer->GetActiveCamera()) {
|
||||
pv->m_Renderer->GetActiveCamera()->RemoveAllObservers();
|
||||
}
|
||||
pv->m_Renderer->RemoveAllObservers();
|
||||
pv->m_Renderer->RemoveAllViewProps();
|
||||
}
|
||||
if (pv->m_Marker && !std::getenv("CTEST_PROJECT_NAME")) {
|
||||
pv->m_Marker->SetEnabled(false);
|
||||
pv->m_Marker->SetInteractor(nullptr);
|
||||
}
|
||||
if (pv->m_CameraWidget && !std::getenv("CTEST_PROJECT_NAME")) {
|
||||
pv->m_CameraWidget->Off();
|
||||
pv->m_CameraWidget->SetInteractor(nullptr);
|
||||
}
|
||||
delete pv;
|
||||
}
|
||||
|
||||
vtkRenderer* Viewport::GetRenderer() { return pv->m_Renderer; }
|
||||
vtkCornerAnnotation* Viewport::GetAnnotation() { return pv->m_Annotation; }
|
||||
vtkCameraOrientationWidget* Viewport::GetCameraWidget() { return pv->m_CameraWidget; }
|
||||
|
||||
void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
{
|
||||
if (!iren) return;
|
||||
@@ -51,49 +116,51 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
iren->SetInteractorStyle(style);
|
||||
|
||||
// Corner annotation
|
||||
m_Annotation->GetTextProperty()->SetColor(1, 1, 1);
|
||||
m_Annotation->GetTextProperty()->SetFontFamilyToArial();
|
||||
m_Annotation->GetTextProperty()->SetOpacity(0.5);
|
||||
m_Annotation->SetMaximumFontSize(10);
|
||||
m_Annotation->SetText(0, "uLib VTK viewer.");
|
||||
m_Renderer->AddViewProp(m_Annotation);
|
||||
pv->m_Annotation->GetTextProperty()->SetColor(1, 1, 1);
|
||||
pv->m_Annotation->GetTextProperty()->SetFontFamilyToArial();
|
||||
pv->m_Annotation->GetTextProperty()->SetOpacity(0.5);
|
||||
pv->m_Annotation->SetMaximumFontSize(10);
|
||||
pv->m_Annotation->SetText(0, "uLib VTK viewer.");
|
||||
pv->m_Renderer->AddViewProp(pv->m_Annotation);
|
||||
|
||||
// right corner annotation
|
||||
m_Annotation->SetText(1, "Grid: -");
|
||||
pv->m_Annotation->SetText(1, "Grid: -");
|
||||
|
||||
// Orientation axes marker (bottom-left corner)
|
||||
vtkNew<vtkAxesActor> axes;
|
||||
m_Marker->SetInteractor(iren);
|
||||
m_Marker->SetOrientationMarker(axes);
|
||||
m_Marker->SetViewport(0.0, 0.0, 0.2, 0.2);
|
||||
m_Marker->SetEnabled(true);
|
||||
m_Marker->InteractiveOff();
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
vtkNew<vtkAxesActor> axes;
|
||||
pv->m_Marker->SetInteractor(iren);
|
||||
pv->m_Marker->SetOrientationMarker(axes);
|
||||
pv->m_Marker->SetViewport(0.0, 0.0, 0.2, 0.2);
|
||||
pv->m_Marker->SetEnabled(true);
|
||||
pv->m_Marker->InteractiveOff();
|
||||
}
|
||||
|
||||
// Grid Plane centered at (0,0,0)
|
||||
m_GridSource = vtkSmartPointer<vtkPlaneSource>::New();
|
||||
m_GridActor = vtkSmartPointer<vtkActor>::New();
|
||||
pv->m_GridSource = vtkSmartPointer<vtkPlaneSource>::New();
|
||||
pv->m_GridActor = vtkSmartPointer<vtkActor>::New();
|
||||
vtkNew<vtkPolyDataMapper> gridMapper;
|
||||
gridMapper->SetInputConnection(m_GridSource->GetOutputPort());
|
||||
gridMapper->SetInputConnection(pv->m_GridSource->GetOutputPort());
|
||||
|
||||
m_GridActor->SetMapper(gridMapper);
|
||||
m_GridActor->GetProperty()->SetRepresentationToWireframe();
|
||||
m_GridActor->GetProperty()->SetColor(0.4, 0.4, 0.4);
|
||||
m_GridActor->GetProperty()->SetLighting(0);
|
||||
m_GridActor->GetProperty()->SetOpacity(0.5);
|
||||
m_GridActor->PickableOff();
|
||||
m_Renderer->AddActor(m_GridActor);
|
||||
pv->m_GridActor->SetMapper(gridMapper);
|
||||
pv->m_GridActor->GetProperty()->SetRepresentationToWireframe();
|
||||
pv->m_GridActor->GetProperty()->SetColor(0.4, 0.4, 0.4);
|
||||
pv->m_GridActor->GetProperty()->SetLighting(0);
|
||||
pv->m_GridActor->GetProperty()->SetOpacity(0.5);
|
||||
pv->m_GridActor->PickableOff();
|
||||
pv->m_Renderer->AddActor(pv->m_GridActor);
|
||||
|
||||
// Global Origin Axes
|
||||
m_OriginAxes = vtkSmartPointer<vtkAxes>::New();
|
||||
m_OriginAxes->SetScaleFactor(1.0); // will be updated
|
||||
pv->m_OriginAxes = vtkSmartPointer<vtkAxes>::New();
|
||||
pv->m_OriginAxes->SetScaleFactor(1.0); // will be updated
|
||||
|
||||
vtkNew<vtkPolyDataMapper> axesMapper;
|
||||
axesMapper->SetInputConnection(m_OriginAxes->GetOutputPort());
|
||||
axesMapper->SetInputConnection(pv->m_OriginAxes->GetOutputPort());
|
||||
|
||||
m_OriginAxesActor = vtkSmartPointer<vtkActor>::New();
|
||||
m_OriginAxesActor->SetMapper(axesMapper);
|
||||
m_OriginAxesActor->PickableOff();
|
||||
m_Renderer->AddActor(m_OriginAxesActor);
|
||||
pv->m_OriginAxesActor = vtkSmartPointer<vtkActor>::New();
|
||||
pv->m_OriginAxesActor->SetMapper(axesMapper);
|
||||
pv->m_OriginAxesActor->PickableOff();
|
||||
pv->m_Renderer->AddActor(pv->m_OriginAxesActor);
|
||||
|
||||
UpdateGrid();
|
||||
|
||||
@@ -104,48 +171,53 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
static_cast<Viewport*>(clientdata)->UpdateGrid();
|
||||
});
|
||||
iren->AddObserver(vtkCommand::InteractionEvent, interactionCallback);
|
||||
m_Renderer->GetActiveCamera()->AddObserver(vtkCommand::ModifiedEvent, interactionCallback);
|
||||
pv->m_Renderer->GetActiveCamera()->AddObserver(vtkCommand::ModifiedEvent, interactionCallback);
|
||||
|
||||
|
||||
// Camera-orientation widget (VTK >= 9)
|
||||
#if VTK_MAJOR_VERSION >= 9
|
||||
m_CameraWidget = vtkSmartPointer<vtkCameraOrientationWidget>::New();
|
||||
m_CameraWidget->SetParentRenderer(m_Renderer);
|
||||
m_CameraWidget->SetInteractor(iren);
|
||||
m_CameraWidget->On();
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
pv->m_CameraWidget = vtkSmartPointer<vtkCameraOrientationWidget>::New();
|
||||
pv->m_CameraWidget->SetParentRenderer(pv->m_Renderer);
|
||||
pv->m_CameraWidget->SetInteractor(iren);
|
||||
pv->m_CameraWidget->On();
|
||||
}
|
||||
#endif
|
||||
m_Renderer->SetBackground(0.15, 0.15, 0.15);
|
||||
m_Renderer->ResetCamera();
|
||||
pv->m_Renderer->SetBackground(0.15, 0.15, 0.15);
|
||||
pv->m_Renderer->ResetCamera();
|
||||
|
||||
// Setup layering for overimposed rendering
|
||||
if (iren->GetRenderWindow()) {
|
||||
iren->GetRenderWindow()->SetNumberOfLayers(2);
|
||||
m_Renderer->SetLayer(0);
|
||||
pv->m_Renderer->SetLayer(0);
|
||||
}
|
||||
|
||||
// Setup Handler Widget
|
||||
m_HandlerWidget = vtkSmartPointer<vtkHandlerWidget>::New();
|
||||
m_HandlerWidget->SetInteractor(iren);
|
||||
m_HandlerWidget->SetCurrentRenderer(m_Renderer);
|
||||
if (m_HandlerWidget->GetOverlayRenderer()) {
|
||||
m_HandlerWidget->GetOverlayRenderer()->SetLayer(1);
|
||||
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
||||
pv->m_HandlerWidget = vtkSmartPointer<vtkHandlerWidget>::New();
|
||||
pv->m_HandlerWidget->SetInteractor(iren);
|
||||
pv->m_HandlerWidget->SetCurrentRenderer(pv->m_Renderer);
|
||||
if (pv->m_HandlerWidget->GetOverlayRenderer()) {
|
||||
pv->m_HandlerWidget->GetOverlayRenderer()->SetLayer(1);
|
||||
}
|
||||
|
||||
// Observe InteractionEvent to update the selected puppet when the widget moves it
|
||||
vtkNew<vtkCallbackCommand> widgetInteractionCallback;
|
||||
widgetInteractionCallback->SetClientData(this);
|
||||
widgetInteractionCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){
|
||||
auto* self = static_cast<Viewport*>(clientdata);
|
||||
for (auto* p : self->m_Puppets) {
|
||||
if (p->IsSelected()) {
|
||||
p->SyncFromVtk();
|
||||
p->Update();
|
||||
}
|
||||
}
|
||||
});
|
||||
pv->m_HandlerWidget->AddObserver(vtkCommand::InteractionEvent, widgetInteractionCallback);
|
||||
}
|
||||
|
||||
// Observe InteractionEvent to update the selected puppet when the widget moves it
|
||||
vtkNew<vtkCallbackCommand> widgetInteractionCallback;
|
||||
widgetInteractionCallback->SetClientData(this);
|
||||
widgetInteractionCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){
|
||||
auto* self = static_cast<Viewport*>(clientdata);
|
||||
for (auto* p : self->m_Puppets) {
|
||||
if (p->IsSelected()) {
|
||||
p->Update();
|
||||
}
|
||||
}
|
||||
});
|
||||
m_HandlerWidget->AddObserver(vtkCommand::InteractionEvent, widgetInteractionCallback);
|
||||
|
||||
// Picking for selection
|
||||
m_Picker = vtkSmartPointer<vtkCellPicker>::New();
|
||||
pv->m_Picker = vtkSmartPointer<vtkCellPicker>::New();
|
||||
vtkNew<vtkCallbackCommand> clickCallback;
|
||||
clickCallback->SetClientData(this);
|
||||
clickCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
|
||||
@@ -153,35 +225,70 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
auto* self = static_cast<Viewport*>(clientdata);
|
||||
|
||||
int* pos = iren->GetEventPosition();
|
||||
self->m_Picker->Pick(pos[0], pos[1], 0, self->m_Renderer);
|
||||
vtkProp* picked = self->m_Picker->GetViewProp();
|
||||
self->pv->m_Picker->Pick(pos[0], pos[1], 0, self->pv->m_Renderer);
|
||||
vtkProp* picked = self->pv->m_Picker->GetViewProp();
|
||||
|
||||
// 1. Recursive helper to check if a container prop contains a target prop
|
||||
std::function<bool(vtkProp*, vtkProp*)> containsProp;
|
||||
containsProp = [&containsProp](vtkProp* container, vtkProp* target) -> bool {
|
||||
if (container == target) return true;
|
||||
vtkPropCollection* parts = nullptr;
|
||||
if (auto* pa = vtkPropAssembly::SafeDownCast(container))
|
||||
parts = pa->GetParts();
|
||||
else if (auto* aa = vtkAssembly::SafeDownCast(container))
|
||||
parts = aa->GetParts();
|
||||
if (parts) {
|
||||
parts->InitTraversal();
|
||||
for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
|
||||
if (containsProp(parts->GetNextProp(), target))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Puppet* target = nullptr;
|
||||
if (picked) {
|
||||
// 2. Find the leaf puppet: the one that contains 'picked' and is not a parent of another that also contains it.
|
||||
// Actually, we can just find all matches and pick the one with most 'nested' prop?
|
||||
// A simpler way: we know 'picked' is the LEAF prop from VTK.
|
||||
// Find a puppet that contains it.
|
||||
Puppet* leafPuppet = nullptr;
|
||||
for (auto* p : self->m_Puppets) {
|
||||
if (p->GetProp() == picked) {
|
||||
target = p;
|
||||
break;
|
||||
if (containsProp(p->GetProp(), picked)) {
|
||||
// If we already have a candidate, check if this one is smaller (nested)
|
||||
if (!leafPuppet || containsProp(leafPuppet->GetProp(), p->GetProp())) {
|
||||
leafPuppet = p;
|
||||
}
|
||||
}
|
||||
auto* propAssembly = vtkPropAssembly::SafeDownCast(p->GetProp());
|
||||
auto* actorAssembly = vtkAssembly::SafeDownCast(p->GetProp());
|
||||
vtkPropCollection* parts = nullptr;
|
||||
if (propAssembly) parts = propAssembly->GetParts();
|
||||
else if (actorAssembly) parts = actorAssembly->GetParts();
|
||||
}
|
||||
|
||||
if (parts) {
|
||||
bool found = false;
|
||||
parts->InitTraversal();
|
||||
for (int i=0; i<parts->GetNumberOfItems(); ++i) {
|
||||
if (parts->GetNextProp() == picked) {
|
||||
found = true;
|
||||
break;
|
||||
if (leafPuppet) {
|
||||
target = leafPuppet;
|
||||
|
||||
// 3. Model-driven hierarchy climb:
|
||||
// If the leaf puppet has a uLib object, climb its parents.
|
||||
// If any parent is an Assembly with GroupSelection=true, select the assembly puppet instead.
|
||||
uLib::Object* currentObj = leafPuppet->GetContent();
|
||||
|
||||
while (currentObj) {
|
||||
// Object doesn't have parent, but AffineTransform does
|
||||
uLib::Object* parentObj = nullptr;
|
||||
if (auto* at = dynamic_cast<uLib::AffineTransform*>(currentObj)) {
|
||||
parentObj = dynamic_cast<uLib::Object*>(at->GetParent());
|
||||
}
|
||||
|
||||
if (auto* parentAsm = dynamic_cast<::uLib::Assembly*>(parentObj)) {
|
||||
if (parentAsm->GetGroupSelection()) {
|
||||
// Find the puppet for this parent assembly
|
||||
auto it = self->m_ObjectToPuppet.find(parentAsm);
|
||||
if (it != self->m_ObjectToPuppet.end()) {
|
||||
target = it->second;
|
||||
// Keep climbing to find even larger groups
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
target = p;
|
||||
break;
|
||||
}
|
||||
currentObj = parentObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,59 +297,59 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
iren->AddObserver(vtkCommand::LeftButtonPressEvent, clickCallback);
|
||||
|
||||
// Keyboard events for widget coordinate frame
|
||||
m_KeyCallback = vtkSmartPointer<vtkCallbackCommand>::New();
|
||||
m_KeyCallback->SetClientData(this);
|
||||
m_KeyCallback->SetCallback([](vtkObject* caller, unsigned long event, void* clientdata, void*){
|
||||
pv->m_KeyCallback = vtkSmartPointer<vtkCallbackCommand>::New();
|
||||
pv->m_KeyCallback->SetClientData(this);
|
||||
pv->m_KeyCallback->SetCallback([](vtkObject* caller, unsigned long event, void* clientdata, void*){
|
||||
auto* iren = static_cast<vtkRenderWindowInteractor*>(caller);
|
||||
auto* self = static_cast<Viewport*>(clientdata);
|
||||
|
||||
std::string key = iren->GetKeySym();
|
||||
bool handled = false;
|
||||
|
||||
if (self->m_HandlerWidget && self->m_HandlerWidget->GetEnabled()) {
|
||||
if (self->pv->m_HandlerWidget && self->pv->m_HandlerWidget->GetEnabled()) {
|
||||
if (key == "l") {
|
||||
if (event == vtkCommand::KeyPressEvent) {
|
||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::LOCAL);
|
||||
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::LOCAL);
|
||||
std::cout << "Widget Frame: LOCAL" << std::endl;
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
else if (key == "g") {
|
||||
if (event == vtkCommand::KeyPressEvent) {
|
||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::GLOBAL);
|
||||
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::GLOBAL);
|
||||
std::cout << "Widget Frame: GLOBAL" << std::endl;
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
else if (key == "c") {
|
||||
if (event == vtkCommand::KeyPressEvent) {
|
||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER);
|
||||
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER);
|
||||
std::cout << "Widget Frame: CENTER" << std::endl;
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
else if (key == "k") {
|
||||
if (event == vtkCommand::KeyPressEvent) {
|
||||
self->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER_LOCAL);
|
||||
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER_LOCAL);
|
||||
std::cout << "Widget Frame: CENTER_LOCAL" << std::endl;
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
else if (key == "1") {
|
||||
if (event == vtkCommand::KeyPressEvent) {
|
||||
self->m_HandlerWidget->SetTranslationEnabled(!self->m_HandlerWidget->GetTranslationEnabled());
|
||||
self->pv->m_HandlerWidget->SetTranslationEnabled(!self->pv->m_HandlerWidget->GetTranslationEnabled());
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
else if (key == "2") {
|
||||
if (event == vtkCommand::KeyPressEvent) {
|
||||
self->m_HandlerWidget->SetRotationEnabled(!self->m_HandlerWidget->GetRotationEnabled());
|
||||
self->pv->m_HandlerWidget->SetRotationEnabled(!self->pv->m_HandlerWidget->GetRotationEnabled());
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
else if (key == "3") {
|
||||
if (event == vtkCommand::KeyPressEvent) {
|
||||
self->m_HandlerWidget->SetScalingEnabled(!self->m_HandlerWidget->GetScalingEnabled());
|
||||
self->pv->m_HandlerWidget->SetScalingEnabled(!self->pv->m_HandlerWidget->GetScalingEnabled());
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
@@ -256,12 +363,12 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
self->m_KeyCallback->SetAbortFlag(1);
|
||||
self->pv->m_KeyCallback->SetAbortFlag(1);
|
||||
iren->Render();
|
||||
}
|
||||
});
|
||||
iren->AddObserver(vtkCommand::KeyPressEvent, m_KeyCallback, 1.0);
|
||||
iren->AddObserver(vtkCommand::CharEvent, m_KeyCallback, 1.0);
|
||||
iren->AddObserver(vtkCommand::KeyPressEvent, pv->m_KeyCallback, 1.0);
|
||||
iren->AddObserver(vtkCommand::CharEvent, pv->m_KeyCallback, 1.0);
|
||||
}
|
||||
|
||||
void Viewport::Reset()
|
||||
@@ -272,15 +379,15 @@ void Viewport::Reset()
|
||||
|
||||
void Viewport::ZoomAuto()
|
||||
{
|
||||
if (m_Renderer) {
|
||||
m_Renderer->ResetCameraClippingRange();
|
||||
m_Renderer->ResetCamera();
|
||||
if (pv->m_Renderer) {
|
||||
pv->m_Renderer->ResetCameraClippingRange();
|
||||
pv->m_Renderer->ResetCamera();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewport::ZoomSelected()
|
||||
{
|
||||
if (!m_Renderer) return;
|
||||
if (!pv->m_Renderer) return;
|
||||
|
||||
Puppet* selected = nullptr;
|
||||
for (auto* p : m_Puppets) {
|
||||
@@ -316,62 +423,117 @@ void Viewport::ZoomSelected()
|
||||
newBounds[2*i+1] = center[i] + 2.5 * current_h;
|
||||
}
|
||||
|
||||
m_Renderer->ResetCamera(newBounds);
|
||||
m_Renderer->ResetCameraClippingRange();
|
||||
pv->m_Renderer->ResetCamera(newBounds);
|
||||
pv->m_Renderer->ResetCameraClippingRange();
|
||||
this->Render();
|
||||
}
|
||||
|
||||
void Viewport::AddPuppet(Puppet& prop)
|
||||
{
|
||||
m_Puppets.push_back(&prop);
|
||||
prop.ConnectRenderer(m_Renderer);
|
||||
this->RegisterPuppet(&prop, false);
|
||||
Render();
|
||||
}
|
||||
|
||||
void Viewport::RemovePuppet(Puppet& prop)
|
||||
{
|
||||
if (prop.IsSelected()) SelectPuppet(nullptr);
|
||||
auto it = std::find(m_Puppets.begin(), m_Puppets.end(), &prop);
|
||||
if (it != m_Puppets.end()) m_Puppets.erase(it);
|
||||
prop.DisconnectRenderer(m_Renderer);
|
||||
this->UnregisterPuppet(&prop);
|
||||
Render();
|
||||
}
|
||||
|
||||
void Viewport::RegisterPuppet(Puppet* p, bool isPart) {
|
||||
if (!p) return;
|
||||
if (std::find(m_Puppets.begin(), m_Puppets.end(), p) != m_Puppets.end()) return;
|
||||
|
||||
m_Puppets.push_back(p);
|
||||
p->ConnectRenderer(pv->m_Renderer);
|
||||
|
||||
// If it's a part of an assembly, we don't want to draw it twice.
|
||||
// Assembly itself already draws its parts.
|
||||
// But we need ConnectRenderer above to allow highliting and property updates.
|
||||
if (isPart) {
|
||||
pv->m_Renderer->RemoveViewProp(p->GetProp());
|
||||
}
|
||||
|
||||
// Get the object and register in map
|
||||
uLib::Object* obj = p->GetContent();
|
||||
|
||||
// If it's an assembly, we need to observe its children
|
||||
if (auto* as = dynamic_cast<::uLib::Vtk::Assembly*>(p)) {
|
||||
this->ObserveContext(as->GetChildrenContext());
|
||||
}
|
||||
|
||||
if (obj) m_ObjectToPuppet[obj] = p;
|
||||
}
|
||||
|
||||
void Viewport::UnregisterPuppet(Puppet* p) {
|
||||
if (!p) return;
|
||||
if (p->IsSelected()) SelectPuppet(nullptr);
|
||||
|
||||
auto it = std::find(m_Puppets.begin(), m_Puppets.end(), p);
|
||||
if (it != m_Puppets.end()) m_Puppets.erase(it);
|
||||
|
||||
// Remove from map
|
||||
for (auto mapIt = m_ObjectToPuppet.begin(); mapIt != m_ObjectToPuppet.end(); ) {
|
||||
if (mapIt->second == p) mapIt = m_ObjectToPuppet.erase(mapIt);
|
||||
else ++mapIt;
|
||||
}
|
||||
|
||||
p->DisconnectRenderer(pv->m_Renderer);
|
||||
}
|
||||
|
||||
void Viewport::ObserveContext(vtkObjectsContext* ctx) {
|
||||
if (!ctx) return;
|
||||
|
||||
// Process existing puppets
|
||||
for (auto const& [obj, puppet] : ctx->GetPuppets()) {
|
||||
this->RegisterPuppet(puppet, true);
|
||||
}
|
||||
|
||||
// Listen for future puppets
|
||||
uLib::Object::connect(ctx, &vtkObjectsContext::PuppetAdded, [this](Puppet* p){
|
||||
this->RegisterPuppet(p, true);
|
||||
});
|
||||
uLib::Object::connect(ctx, &vtkObjectsContext::PuppetRemoved, [this](Puppet* p){
|
||||
this->UnregisterPuppet(p);
|
||||
});
|
||||
}
|
||||
|
||||
void Viewport::SelectPuppet(Puppet* prop)
|
||||
{
|
||||
for (auto* p : m_Puppets) {
|
||||
p->SetSelected(p == prop);
|
||||
}
|
||||
|
||||
if (m_HandlerWidget) {
|
||||
if (pv->m_HandlerWidget) {
|
||||
if (prop) {
|
||||
vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp());
|
||||
if (prop3d) {
|
||||
m_HandlerWidget->SetProp3D(prop3d);
|
||||
m_HandlerWidget->SetEnabled(1);
|
||||
m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
|
||||
pv->m_HandlerWidget->SetProp3D(prop3d);
|
||||
pv->m_HandlerWidget->SetEnabled(1);
|
||||
pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
|
||||
}
|
||||
} else {
|
||||
m_HandlerWidget->SetEnabled(0);
|
||||
m_HandlerWidget->SetProp3D(nullptr);
|
||||
pv->m_HandlerWidget->SetEnabled(0);
|
||||
pv->m_HandlerWidget->SetProp3D(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Render();
|
||||
OnSelectionChanged(prop);
|
||||
}
|
||||
|
||||
void Viewport::SetGridVisible(bool visible)
|
||||
{
|
||||
if (m_GridActor) {
|
||||
m_GridActor->SetVisibility(visible);
|
||||
if (pv->m_GridActor) {
|
||||
pv->m_GridActor->SetVisibility(visible);
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
bool Viewport::GetGridVisible() const
|
||||
{
|
||||
if (m_GridActor) {
|
||||
return m_GridActor->GetVisibility() != 0;
|
||||
if (pv->m_GridActor) {
|
||||
return pv->m_GridActor->GetVisibility() != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -385,26 +547,26 @@ void Viewport::SetGridAxis(Axis axis)
|
||||
|
||||
void Viewport::addProp(vtkProp* prop)
|
||||
{
|
||||
if (m_Renderer) {
|
||||
m_Renderer->AddActor(prop);
|
||||
if (pv->m_Renderer) {
|
||||
pv->m_Renderer->AddActor(prop);
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewport::RemoveProp(vtkProp* prop)
|
||||
{
|
||||
if (m_Renderer) {
|
||||
m_Renderer->RemoveViewProp(prop);
|
||||
if (pv->m_Renderer) {
|
||||
pv->m_Renderer->RemoveViewProp(prop);
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewport::UpdateGrid()
|
||||
{
|
||||
if (!m_Renderer || !m_GridSource) return;
|
||||
if (m_GridActor && !m_GridActor->GetVisibility()) return;
|
||||
if (!pv->m_Renderer || !pv->m_GridSource) return;
|
||||
if (pv->m_GridActor && !pv->m_GridActor->GetVisibility()) return;
|
||||
|
||||
vtkCamera* camera = m_Renderer->GetActiveCamera();
|
||||
vtkCamera* camera = pv->m_Renderer->GetActiveCamera();
|
||||
if (!camera) return;
|
||||
|
||||
// Determine the "scale" of the view (how many units are visible vertically)
|
||||
@@ -425,6 +587,9 @@ void Viewport::UpdateGrid()
|
||||
double log10Spacing = std::floor(std::log10(viewHeight / 5.0));
|
||||
double spacing = std::pow(10.0, log10Spacing);
|
||||
|
||||
// Spacing should be at least 1mm
|
||||
if (spacing < 1.0) spacing = 1.0;
|
||||
|
||||
// Get current focal point to center the grid near what we're looking at
|
||||
double focalPoint[3];
|
||||
camera->GetFocalPoint(focalPoint);
|
||||
@@ -455,31 +620,28 @@ void Viewport::UpdateGrid()
|
||||
p1[idxH] = maxH; p1[idxV] = minV; p1[idxN] = centerN;
|
||||
p2[idxH] = minH; p2[idxV] = maxV; p2[idxN] = centerN;
|
||||
|
||||
m_GridSource->SetOrigin(origin);
|
||||
m_GridSource->SetPoint1(p1);
|
||||
m_GridSource->SetPoint2(p2);
|
||||
m_GridSource->SetXResolution(numLines);
|
||||
m_GridSource->SetYResolution(numLines);
|
||||
m_GridSource->Update();
|
||||
pv->m_GridSource->SetOrigin(origin);
|
||||
pv->m_GridSource->SetPoint1(p1);
|
||||
pv->m_GridSource->SetPoint2(p2);
|
||||
pv->m_GridSource->SetXResolution(numLines);
|
||||
pv->m_GridSource->SetYResolution(numLines);
|
||||
pv->m_GridSource->Update();
|
||||
|
||||
if (m_OriginAxes) {
|
||||
m_OriginAxes->SetScaleFactor(spacing);
|
||||
if (pv->m_OriginAxes) {
|
||||
pv->m_OriginAxes->SetScaleFactor(spacing);
|
||||
}
|
||||
|
||||
// Update annotation for grid size
|
||||
char gridLabel[32];
|
||||
if (spacing >= 1000.0) {
|
||||
sprintf(gridLabel, "Grid: %.0f m", spacing / 1000.0);
|
||||
sprintf(gridLabel, "Grid: %.1f m", spacing / 1000.0);
|
||||
} else if (spacing >= 10.0) {
|
||||
sprintf(gridLabel, "Grid: %.0f cm", spacing / 10.0);
|
||||
sprintf(gridLabel, "Grid: %.1f cm", spacing / 10.0);
|
||||
} else {
|
||||
sprintf(gridLabel, "Grid: %.0f mm", spacing);
|
||||
}
|
||||
m_Annotation->SetText(1, gridLabel);
|
||||
pv->m_Annotation->SetText(1, gridLabel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Vtk
|
||||
} // namespace uLib
|
||||
|
||||
@@ -2,23 +2,22 @@
|
||||
#define ULIB_VTK_VIEWPORT_H
|
||||
|
||||
#include "uLibVtkInterface.h"
|
||||
#include <vtkCornerAnnotation.h>
|
||||
#include <vtkOrientationMarkerWidget.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkVersion.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkCornerAnnotation.h>
|
||||
#include <vtkOrientationMarkerWidget.h>
|
||||
#include <vtkCameraOrientationWidget.h>
|
||||
#include <vtkNamedColors.h>
|
||||
#include <vtkAxes.h>
|
||||
#include <vtkAxesActor.h>
|
||||
#include <vtkPlaneSource.h>
|
||||
#include <vtkActor.h>
|
||||
#include <vtkCellPicker.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace uLib { class Object; }
|
||||
|
||||
// VTK classes are in the global namespace
|
||||
class vtkRenderer;
|
||||
class vtkCornerAnnotation;
|
||||
class vtkOrientationMarkerWidget;
|
||||
class vtkCameraOrientationWidget;
|
||||
class vtkPlaneSource;
|
||||
class vtkActor;
|
||||
class vtkAxes;
|
||||
class vtkNamedColors;
|
||||
class vtkCellPicker;
|
||||
class vtkCallbackCommand;
|
||||
class vtkProp;
|
||||
class vtk3DWidget;
|
||||
class vtkRenderWindow;
|
||||
@@ -28,6 +27,7 @@ class vtkCamera;
|
||||
namespace uLib {
|
||||
namespace Vtk {
|
||||
|
||||
struct ViewportData;
|
||||
class vtkHandlerWidget;
|
||||
|
||||
/**
|
||||
@@ -56,11 +56,13 @@ public:
|
||||
void AddWidget(vtk3DWidget *widget);
|
||||
|
||||
// Direct access to VTK internals
|
||||
vtkRenderer* GetRenderer() { return m_Renderer; }
|
||||
vtkRenderer* GetRenderer();
|
||||
virtual vtkRenderWindow* GetRenderWindow() = 0;
|
||||
virtual vtkRenderWindowInteractor* GetInteractor() = 0;
|
||||
vtkCornerAnnotation* GetAnnotation() { return m_Annotation; }
|
||||
vtkCameraOrientationWidget* GetCameraWidget(){ return m_CameraWidget; }
|
||||
vtkCornerAnnotation* GetAnnotation();
|
||||
vtkCameraOrientationWidget* GetCameraWidget();
|
||||
void DisableHandler();
|
||||
virtual void OnSelectionChanged(Puppet* p) {}
|
||||
const std::vector<Puppet*>& getPuppets() const { return m_Puppets; }
|
||||
|
||||
// Grid control
|
||||
@@ -75,24 +77,16 @@ protected:
|
||||
void SetupPipeline(vtkRenderWindowInteractor* iren);
|
||||
|
||||
void UpdateGrid();
|
||||
|
||||
vtkSmartPointer<vtkRenderer> m_Renderer;
|
||||
vtkSmartPointer<vtkCornerAnnotation> m_Annotation;
|
||||
vtkSmartPointer<vtkOrientationMarkerWidget> m_Marker;
|
||||
vtkSmartPointer<vtkCameraOrientationWidget> m_CameraWidget;
|
||||
|
||||
vtkSmartPointer<vtkPlaneSource> m_GridSource;
|
||||
vtkSmartPointer<vtkActor> m_GridActor;
|
||||
vtkSmartPointer<vtkAxes> m_OriginAxes;
|
||||
vtkSmartPointer<vtkActor> m_OriginAxesActor;
|
||||
|
||||
vtkSmartPointer<vtkNamedColors> m_Colors;
|
||||
|
||||
// Internal puppet registration
|
||||
void RegisterPuppet(Puppet* p, bool isPart = false);
|
||||
void UnregisterPuppet(Puppet* p);
|
||||
void ObserveContext(class vtkObjectsContext* ctx);
|
||||
|
||||
struct ViewportData *pv;
|
||||
Axis m_GridAxis;
|
||||
vtkSmartPointer<vtkHandlerWidget> m_HandlerWidget;
|
||||
std::vector<Puppet*> m_Puppets;
|
||||
vtkSmartPointer<vtkCellPicker> m_Picker;
|
||||
vtkSmartPointer<vtkCallbackCommand> m_KeyCallback;
|
||||
std::map<uLib::Object*, Puppet*> m_ObjectToPuppet;
|
||||
};
|
||||
|
||||
} // namespace Vtk
|
||||
|
||||
Reference in New Issue
Block a user