diff --git a/docs/transformation_system.md b/docs/transformation_system.md new file mode 100644 index 0000000..a5cc8f8 --- /dev/null +++ b/docs/transformation_system.md @@ -0,0 +1,73 @@ +# Transformation Flow and Synchronization System + +This document describes how transformations are applied and synchronized between the interactive 3D viewport, the visualization puppets, and the underlying mathematical models within the `uLib` framework. + +## Architecture Overview + +The system follows a Model-View-Controller (MVC) like pattern where: +- **Model**: `uLib::AffineTransform` (or derived classes like `ContainerBox`). +- **View/Puppet**: `uLib::Vtk::Puppet` (and specialized derivations like `Vtk::Assembly`). +- **Controller/Interaction**: `vtkHandlerWidget` (the transformation gizmo). + +--- + +## 1. Interaction Flow (Gizmo -> Model) + +When a user interacts with the `vtkHandlerWidget` (dragging arrows, rings, or cubes), the following chain of events occurs: + +```mermaid +sequenceDiagram + participant User + participant HW as vtkHandlerWidget + participant VP as vtkViewport + participant P as vtkPuppet + participant M as uLib Model + + User->>HW: Drag handle (MouseMove) + HW->>HW: Calculate Delta Matrix (op) + HW->>HW: Total = StartState * op + HW->>HW: Decompose Total into P, O, S + HW->>P: SetPosition, SetOrientation, SetScale + HW-->>VP: Invoke InteractionEvent + VP->>P: SyncFromVtk() + P->>P: Get local matrix from VTK Prop + P->>M: SetMatrix(matrix) + M-->>M: Update local properties (P, O, S) + M-->>P: Emit Updated signal + P->>P: Puppet::Update() + P->>P: (Redundant sanity write to Prop) +``` + +### Key Principles: +- **Single Source of Truth**: The `uLib::AffineTransform` is the owner of the transformation state. +- **Internal TRS vs UserMatrix**: We apply transformations directly to VTK's internal `Position`, `Orientation`, and `Scale` properties. This ensures the data is "visible" to VTK actors and simplifies decomposition. +- **Cumulative Bias Avoidance**: The `HandlerWidget` calculates transformations relative to the state at the start of the click, preventing numerical drift during a single drag operation. + +--- + +## 2. Synchronization Loop Resolution + +To prevent infinite loops and "double-transformation" artifacts (especially in assemblies), the following protections are in place: + +1. **Hierarchy Isolation**: The `Puppet` base class distinguishes between the **Root Property** (which receives the puppet's master transformation) and **Sub-Parts** (which only receive appearance updates like color/visibility). This prevents parts from inheriting the same displacement twice. +2. **Re-entrancy Guards**: Puppets use an `m_InUpdate` flag to prevent a feedback loop where `SyncFromVtk` triggers a Model Update, which then re-triggers the Puppet Update. +3. **Signal Blocking**: In specialized cases (like `vtkAssembly`), `m_BlockUpdate` is used to prevent the model-to-puppet push during a puppet-to-model sync. + +--- + +## 3. Undo System (Ctrl-Z) + +### Current Implementation (Delta Chain) +Currently, the system maintains a `m_TransformChain` of delta matrices. +- **Record**: After every drag, a delta matrix ($M_{delta} = M_{end} \cdot M_{start}^{-1}$) is appended to the chain. +- **Undo**: The last delta is removed, and the prop is reconstructed by reapplying the remaining chain from a `BaseMatrix`. + +### Planned Improvement (TRS Snapshots) +We are migrating to a `uLib::TRS` snapshot system for Undo. +- **Record**: At the start of a drag, the current `TRS` state of the object is pushed to the `m_UndoStack`. +- **Undo**: The top `TRS` is popped and applied directly to the model. + +This approach is more robust because: +- It eliminates matrix multiplication error accumulation. +- It bypasses rotation convention/order issues (Gimbal lock in deltas). +- It returns the object to exactly its previous property values. diff --git a/src/Math/Transform.h b/src/Math/Transform.h index 70ab729..dd4a42f 100644 --- a/src/Math/Transform.h +++ b/src/Math/Transform.h @@ -56,6 +56,67 @@ namespace uLib { + +//////////////////////////////////////////////////////////////////////////////// +///////// TRS PARAMETERS ///////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +typedef Eigen::Affine3f AffineMatrix; + +class TRS { +public: + Vector3f position = Vector3f::Zero(); + Vector3f rotation = Vector3f::Zero(); + Vector3f scaling = Vector3f::Ones(); + + TRS() = default; + + TRS(const class AffineTransform& at); + + TRS(const Matrix4f& mat) { + this->FromMatrix(mat); + } + + void FromMatrix(const Matrix4f& mat) { + this->position = mat.block<3,1>(0,3); + + Matrix3f linear = mat.block<3,3>(0,0); + this->scaling(0) = linear.col(0).norm(); + this->scaling(1) = linear.col(1).norm(); + this->scaling(2) = linear.col(2).norm(); + + Matrix3f rot = linear; + if (this->scaling(0) > 1e-6) rot.col(0) /= this->scaling(0); + if (this->scaling(1) > 1e-6) rot.col(1) /= this->scaling(1); + if (this->scaling(2) > 1e-6) rot.col(2) /= this->scaling(2); + + // Decompose to Euler angles matching VTK (M = Rz * Ry * Rx) + // Store internally as RADIANS (standard for uLib properties) + Vector3f euler = rot.eulerAngles(2, 1, 0); + this->rotation = Vector3f(euler(2), euler(1), euler(0)); + } + + template + void serialize(ArchiveT & ar, const unsigned int version) { + ar & HRPU(position, "mm"); + ar & HRPU(rotation, "deg"); // Metadata informs UI to convert to/from degrees + ar & HRP(scaling); + } + + AffineMatrix GetAffineMatrix() const { + AffineMatrix t = AffineMatrix::Identity(); + t.translate(position); + + // rotation is in Radians here + t.rotate(Eigen::AngleAxisf(rotation.z(), Vector3f::UnitZ())); + t.rotate(Eigen::AngleAxisf(rotation.y(), Vector3f::UnitY())); + t.rotate(Eigen::AngleAxisf(rotation.x(), Vector3f::UnitX())); + + t.scale(scaling); + return t; + } +}; + //////////////////////////////////////////////////////////////////////////////// ///////// AFFINE TRANSFORM WRAPPER ////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -64,21 +125,15 @@ class AffineTransform : virtual public Object { public: uLibTypeMacro(AffineTransform, Object) - /** - * @brief Grouped transformation parameters for property-based control. - */ - struct { - Vector3f Position = Vector3f::Zero(); - Vector3f Orientation = Vector3f::Zero(); - Vector3f Scale = Vector3f::Ones(); + TRS Transform; - template - void serialize(ArchiveT & ar, const unsigned int version) { - ar & HRPU(Position, "mm"); - ar & HRPU(Orientation, "deg"); - ar & HRP(Scale); - } - } Transform; +private: + void NotifyProperties() { + PropertyBase *p; + if ((p = this->GetProperty("Transform.position"))) p->Updated(); + if ((p = this->GetProperty("Transform.rotation"))) p->Updated(); + if ((p = this->GetProperty("Transform.scaling"))) p->Updated(); + } protected: Eigen::Affine3f m_T; @@ -140,11 +195,25 @@ public: } void SetPosition(const Vector3f v) { - this->Transform.Position = v; - this->Sync(); + this->Transform.position = v; + this->Updated(); + this->NotifyProperties(); } + Vector3f GetPosition() const { return this->Transform.position; } - Vector3f GetPosition() const { return this->Transform.Position; } + void SetOrientation(const Vector3f v) { + this->Transform.rotation = v; + this->Updated(); + this->NotifyProperties(); + } + Vector3f GetOrientation() const { return this->Transform.rotation; } + + void SetScale(const Vector3f v) { + this->Transform.scaling = v; + this->Updated(); + this->NotifyProperties(); + } + Vector3f GetScale() const { return this->Transform.scaling; } void SetRotation(const Matrix3f m) { this->m_T.linear() = m; @@ -154,24 +223,15 @@ public: Matrix3f GetRotation() const { return this->m_T.rotation(); } void Translate(const Vector3f v) { - this->Transform.Position += v; + this->Transform.position += v; this->Sync(); } void Scale(const Vector3f v) { - this->Transform.Scale = this->Transform.Scale.cwiseProduct(v); + this->Transform.scaling = this->Transform.scaling.cwiseProduct(v); this->Sync(); } - Vector3f GetScale() const { return this->Transform.Scale; } - - void SetOrientation(const Vector3f v) { - this->Transform.Orientation = v; - this->Sync(); - } - - Vector3f GetOrientation() const { return this->Transform.Orientation; } - void Rotate(const Matrix3f m) { this->m_T.rotate(m); @@ -197,7 +257,7 @@ public: { this->m_T.rotate(Eigen::Quaternion(q)); this->UpdatePropertiesFromMatrix(); } void EulerYZYRotate(const Vector3f e) { - this->Transform.Orientation = e; + this->Transform.rotation = e; this->Sync(); } @@ -213,30 +273,12 @@ public: * @brief Decomposes the internal matrix m_T back into Position, Orientation, and Scale properties. */ void UpdatePropertiesFromMatrix() { - // 1. Position - Transform.Position = m_T.translation(); + this->Transform.FromMatrix(this->GetMatrix()); - // 2. Scale - Matrix3f linear = m_T.linear(); - Transform.Scale(0) = linear.col(0).norm(); - Transform.Scale(1) = linear.col(1).norm(); - Transform.Scale(2) = linear.col(2).norm(); - - // 3. Rotation (Normalization removes scale) - Matrix3f rotation = linear; - if (Transform.Scale(0) > 1e-6) rotation.col(0) /= Transform.Scale(0); - if (Transform.Scale(1) > 1e-6) rotation.col(1) /= Transform.Scale(1); - if (Transform.Scale(2) > 1e-6) rotation.col(2) /= Transform.Scale(2); - - // Euler YZY (indices 1, 2, 1) - Vector3f euler = rotation.eulerAngles(1, 2, 1); - Transform.Orientation = euler / CLHEP::degree; - - // Notify properties - PropertyBase* p; - if ((p = this->GetProperty("Transform.Position"))) p->Updated(); - if ((p = this->GetProperty("Transform.Orientation"))) p->Updated(); - if ((p = this->GetProperty("Transform.Scale"))) p->Updated(); + PropertyBase *p; + if ((p = this->GetProperty("Transform.position"))) p->Updated(); + if ((p = this->GetProperty("Transform.rotation"))) p->Updated(); + if ((p = this->GetProperty("Transform.scaling"))) p->Updated(); } signals: @@ -247,22 +289,19 @@ signals: } private: - /** Synchronizes m_T with properties */ - void Sync() { - m_T = Eigen::Affine3f::Identity(); - m_T.translate(Transform.Position); - - // Orientation (using YZY order as implied by EulerYZYRotate) - Matrix3f mat; - mat = Eigen::AngleAxisf(Transform.Orientation.x() * CLHEP::degree, Vector3f::UnitY()) - * Eigen::AngleAxisf(Transform.Orientation.y() * CLHEP::degree, Vector3f::UnitZ()) - * Eigen::AngleAxisf(Transform.Orientation.z() * CLHEP::degree, Vector3f::UnitY()); - m_T.rotate(mat); - - m_T.scale(Transform.Scale); - } + void Sync() { + m_T.matrix() = this->Transform.GetAffineMatrix().matrix(); + } }; +inline TRS::TRS(const AffineTransform& at) { + this->position = at.GetPosition(); + this->rotation = at.GetOrientation(); + this->scaling = at.GetScale(); +} + + + } diff --git a/src/Vtk/CMakeLists.txt b/src/Vtk/CMakeLists.txt index 6517431..9600b8f 100644 --- a/src/Vtk/CMakeLists.txt +++ b/src/Vtk/CMakeLists.txt @@ -3,7 +3,6 @@ set(HEADERS uLibVtkInterface.h vtkHandlerWidget.h vtkQViewport.h vtkViewport.h - vtkPolydata.h vtkObjectsContext.h ) @@ -12,7 +11,6 @@ set(SOURCES uLibVtkInterface.cxx vtkHandlerWidget.cpp vtkQViewport.cpp vtkViewport.cpp - vtkPolydata.cpp vtkObjectsContext.cpp ) diff --git a/src/Vtk/HEP/Detectors/vtkMuonEvent.h b/src/Vtk/HEP/Detectors/vtkMuonEvent.h index 0c3abf6..068c9a8 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonEvent.h +++ b/src/Vtk/HEP/Detectors/vtkMuonEvent.h @@ -45,7 +45,7 @@ #include "HEP/Detectors/MuonEvent.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" namespace uLib { namespace Vtk { diff --git a/src/Vtk/HEP/Detectors/vtkMuonScatter.h b/src/Vtk/HEP/Detectors/vtkMuonScatter.h index ac7c85f..6eda4ba 100644 --- a/src/Vtk/HEP/Detectors/vtkMuonScatter.h +++ b/src/Vtk/HEP/Detectors/vtkMuonScatter.h @@ -46,7 +46,7 @@ #include "HEP/Detectors/MuonScatter.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkRenderWindowInteractor; diff --git a/src/Vtk/HEP/Geant/vtkGeantEvent.h b/src/Vtk/HEP/Geant/vtkGeantEvent.h index c904baf..f139bcf 100644 --- a/src/Vtk/HEP/Geant/vtkGeantEvent.h +++ b/src/Vtk/HEP/Geant/vtkGeantEvent.h @@ -28,7 +28,7 @@ #include "HEP/Geant/GeantEvent.h" #include "uLibVtkInterface.h" -#include "vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" #include namespace uLib { diff --git a/src/Vtk/HEP/Geant/vtkGeantSolid.h b/src/Vtk/HEP/Geant/vtkGeantSolid.h index 8fb127d..e919577 100644 --- a/src/Vtk/HEP/Geant/vtkGeantSolid.h +++ b/src/Vtk/HEP/Geant/vtkGeantSolid.h @@ -28,7 +28,7 @@ #include "HEP/Geant/Solid.h" #include "uLibVtkInterface.h" -#include "vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkActor; diff --git a/src/Vtk/vtkuLibProp.h b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.cpp similarity index 83% rename from src/Vtk/vtkuLibProp.h rename to src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.cpp index fd6be35..b49a65e 100644 --- a/src/Vtk/vtkuLibProp.h +++ b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.cpp @@ -25,27 +25,11 @@ -#ifndef U_VTKULIBPROP_H -#define U_VTKULIBPROP_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif -class vtkProp; - -namespace uLib { -namespace Abstract { - -class uLibVtkProp { -public: - virtual vtkProp *GetProp() = 0; - -protected: - ~uLibVtkProp() {} -}; +#include - - -} - -} - -#endif // VTKULIBPROP_H +// TO BE CONTINUED // diff --git a/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.h b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.h new file mode 100644 index 0000000..d7f12d9 --- /dev/null +++ b/src/Vtk/HEP/MuonTomography/vtkMuonContainerScattering.h @@ -0,0 +1,74 @@ +/*////////////////////////////////////////////////////////////////////////////// +// 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 VTKMUONCONTAINERSCATTERING_H +#define VTKMUONCONTAINERSCATTERING_H + + + +#include "Math/Dense.h" + +#include "uLibVtkInterface.h" +#include "Detectors/MuonScatter.h" + +class vtkRenderWindowInteractor; + +namespace uLib { + +class vtkMuonContainerScattering : public Abstract::uLibVtkPolydata { + typedef MuonScatter Content; +public: + vtkMuonContainerScattering(const MuonScatter &content); + ~vtkMuonScatter(); + + Content& GetContent(); + + void PrintSelf(std::ostream &o) const; + + virtual vtkProp *GetProp(); + + virtual vtkPolyData* GetPolyData() const; + + void AddPocaPoint(HPoint3f poca); + + HPoint3f GetPocaPoint(); + + void vtkStartInteractive(); + +protected: + void ConnectInteractor(vtkRenderWindowInteractor *interactor); + +private: + void InstallPipe(); + +}; + + +} + + +#endif // VTKMUONCONTAINERSCATTERING_H diff --git a/src/Vtk/Math/CMakeLists.txt b/src/Vtk/Math/CMakeLists.txt index 0dbe4c3..cd1c110 100644 --- a/src/Vtk/Math/CMakeLists.txt +++ b/src/Vtk/Math/CMakeLists.txt @@ -11,6 +11,7 @@ set(MATH_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/vtkPolydata.cpp PARENT_SCOPE) set(MATH_HEADERS @@ -22,6 +23,7 @@ set(MATH_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/vtkContainerBox.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkCylinder.h ${CMAKE_CURRENT_SOURCE_DIR}/vtkAssembly.h + ${CMAKE_CURRENT_SOURCE_DIR}/vtkPolydata.h PARENT_SCOPE) if(BUILD_TESTING) diff --git a/src/Vtk/Math/vtkAssembly.cpp b/src/Vtk/Math/vtkAssembly.cpp index 19a3fd7..d25941d 100644 --- a/src/Vtk/Math/vtkAssembly.cpp +++ b/src/Vtk/Math/vtkAssembly.cpp @@ -92,13 +92,12 @@ void Assembly::InstallPipe() { void Assembly::contentUpdate() { if (m_InUpdate) return; m_InUpdate = true; - + m_BlockUpdate = false; this->UpdateTransform(); this->UpdateBoundingBox(); if (m_ChildContext) m_ChildContext->Update(); - m_BlockUpdate = true; Puppet::Update(); m_InUpdate = false; } @@ -106,25 +105,29 @@ void Assembly::contentUpdate() { // ------------------------------------------------------------------ // void Assembly::Update() { if (m_InUpdate) return; - if (!m_Content || !m_VtkAsm) return; + m_InUpdate = true; + this->contentUpdate(); + m_InUpdate = false; +} - if (m_BlockUpdate) { - m_BlockUpdate = false; - return; - } +void Assembly::SyncFromVtk() { + if (m_InUpdate) return; + if (!m_Content || !m_VtkAsm) 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); - } + double pos[3], ori[3], scale[3]; + m_VtkAsm->GetPosition(pos); + m_VtkAsm->GetOrientation(ori); + m_VtkAsm->GetScale(scale); + + m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); + m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); + m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); this->UpdateBoundingBox(); if (m_ChildContext) - m_ChildContext->Update(); + m_ChildContext->SyncFromVtk(); m_Content->Updated(); // Notify change in model @@ -135,10 +138,7 @@ void Assembly::Update() { void Assembly::UpdateTransform() { if (!m_Content || !m_VtkAsm) return; - Matrix4f mat = m_Content->GetMatrix(); - vtkNew vmat; - Matrix4fToVtk(mat, vmat); - m_VtkAsm->SetUserMatrix(vmat); + this->ApplyTransform(m_VtkAsm); m_VtkAsm->Modified(); } diff --git a/src/Vtk/Math/vtkAssembly.h b/src/Vtk/Math/vtkAssembly.h index 297a9c0..a63d54f 100644 --- a/src/Vtk/Math/vtkAssembly.h +++ b/src/Vtk/Math/vtkAssembly.h @@ -44,6 +44,9 @@ public: /** @brief Updates the VTK representation from the model (model→VTK). */ virtual void Update() override; + /** @brief Synchronizes the model from the VTK representation (VTK→model). */ + virtual void SyncFromVtk() override; + virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } /** @brief Called when the model signals an update (model→VTK push). */ diff --git a/src/Vtk/Math/vtkContainerBox.cpp b/src/Vtk/Math/vtkContainerBox.cpp index 2b8a37f..f4ffa56 100644 --- a/src/Vtk/Math/vtkContainerBox.cpp +++ b/src/Vtk/Math/vtkContainerBox.cpp @@ -80,54 +80,38 @@ void vtkContainerBox::contentUpdate() { vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); if (!root) return; - vtkMatrix4x4* vmat = root->GetUserMatrix(); - if (!vmat) { - // Should have been set in InstallPipe, but let's be safe - vtkNew mat; - root->SetUserMatrix(mat); - vmat = mat; - } - d->m_Cube->SetUserMatrix(nullptr); d->m_Axes->SetUserMatrix(nullptr); - Matrix4f transform = m_Content->GetMatrix(); - Matrix4fToVtk(transform, vmat); + TRS trs(*m_Content); + this->ApplyTransform(root); root->Modified(); - m_BlockUpdate = true; + m_BlockUpdate = false; Puppet::Update(); } void vtkContainerBox::Update() { + this->contentUpdate(); +} + +void vtkContainerBox::SyncFromVtk() { RecursiveMutex::ScopedLock lock(this->m_UpdateMutex); if (!m_Content) return; - if (m_BlockUpdate) { - m_BlockUpdate = false; - return; - } - - // Use Targeted Blocking: only block the feedback connection to this puppet - // boost::signals2::shared_connection_block block(m_Connection); - vtkProp3D* assembly = vtkProp3D::SafeDownCast(this->GetProp()); if (!assembly) return; - vtkMatrix4x4* vmat = assembly->GetUserMatrix(); - if (!vmat) return; + double pos[3], ori[3], scale[3]; + assembly->GetPosition(pos); + assembly->GetOrientation(ori); + assembly->GetScale(scale); + + m_Content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); + m_Content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); + m_Content->SetScale(Vector3f(scale[0], scale[1], scale[2])); - Matrix4f transform = VtkToMatrix4f(vmat); - - // Update uLib model's affine transform - // if (m_Content->GetParent()) { - // Matrix4f localT = m_Content->GetParent()->GetWorldMatrix().inverse() * transform; - // m_Content->SetMatrix(localT); - // } else { - m_Content->SetMatrix(transform); - // } - m_Content->Updated(); // Notify change } @@ -175,9 +159,11 @@ void vtkContainerBox::InstallPipe() { vtkProp3D* root = vtkProp3D::SafeDownCast(this->GetProp()); if (root) { - vtkNew vmat; - Matrix4fToVtk(c->GetMatrix(), vmat); - root->SetUserMatrix(vmat); + TRS trs(*c); + root->SetPosition(trs.position.x(), trs.position.y(), trs.position.z()); + root->SetOrientation(trs.rotation.x(), trs.rotation.y(), trs.rotation.z()); + root->SetScale(trs.scaling.x(), trs.scaling.y(), trs.scaling.z()); + root->SetUserMatrix(nullptr); } } diff --git a/src/Vtk/Math/vtkContainerBox.h b/src/Vtk/Math/vtkContainerBox.h index 4b4bfd6..6fe0873 100644 --- a/src/Vtk/Math/vtkContainerBox.h +++ b/src/Vtk/Math/vtkContainerBox.h @@ -49,7 +49,8 @@ public: virtual void contentUpdate(); - virtual void Update(); + virtual void Update() override; + virtual void SyncFromVtk() override; virtual uLib::Object* GetContent() const override { return (uLib::Object*)m_Content; } diff --git a/src/Vtk/vtkPolydata.cpp b/src/Vtk/Math/vtkPolydata.cpp similarity index 100% rename from src/Vtk/vtkPolydata.cpp rename to src/Vtk/Math/vtkPolydata.cpp diff --git a/src/Vtk/vtkPolydata.h b/src/Vtk/Math/vtkPolydata.h similarity index 100% rename from src/Vtk/vtkPolydata.h rename to src/Vtk/Math/vtkPolydata.h diff --git a/src/Vtk/Math/vtkQuadMesh.h b/src/Vtk/Math/vtkQuadMesh.h index b34282a..abd7e3b 100644 --- a/src/Vtk/Math/vtkQuadMesh.h +++ b/src/Vtk/Math/vtkQuadMesh.h @@ -28,7 +28,7 @@ #include "Math/QuadMesh.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkPolyData; class vtkActor; diff --git a/src/Vtk/Math/vtkTriangleMesh.h b/src/Vtk/Math/vtkTriangleMesh.h index 6ef07d7..56d9607 100644 --- a/src/Vtk/Math/vtkTriangleMesh.h +++ b/src/Vtk/Math/vtkTriangleMesh.h @@ -28,7 +28,7 @@ #include "Math/TriangleMesh.h" #include "Vtk/uLibVtkInterface.h" -#include "Vtk/vtkPolydata.h" +#include "Vtk/Math/vtkPolydata.h" class vtkPolyData; class vtkActor; diff --git a/src/Vtk/uLibVtkInterface.cxx b/src/Vtk/uLibVtkInterface.cxx index 2163f24..97e525f 100644 --- a/src/Vtk/uLibVtkInterface.cxx +++ b/src/Vtk/uLibVtkInterface.cxx @@ -63,6 +63,7 @@ #include "uLibVtkInterface.h" #include "vtkHandlerWidget.h" #include "Math/Dense.h" +#include "Vtk/Math/vtkDense.h" #include "Core/Property.h" @@ -75,12 +76,6 @@ namespace uLib { namespace Vtk { - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// PUPPET // - // PIMPL -------------------------------------------------------------------- // class PuppetData { @@ -98,9 +93,6 @@ public: 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() { @@ -126,9 +118,7 @@ public: bool m_Selected; bool m_Visibility; bool m_Dragable; - Vector3d m_Position; - Vector3d m_Orientation; - Vector3d m_Scale; + TRS m_Transform; void ApplyAppearance(vtkProp *p) { p->SetVisibility(m_Visibility); @@ -154,13 +144,19 @@ public: 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()); + void ApplyTransform(vtkProp3D* p3d) { + if (p3d) { + p3d->SetPosition(m_Transform.position.x(), m_Transform.position.y(), m_Transform.position.z()); + + // Convert Model Radians to VTK Degrees + p3d->SetOrientation(m_Transform.rotation.x() / CLHEP::degree, + m_Transform.rotation.y() / CLHEP::degree, + m_Transform.rotation.z() / CLHEP::degree); + + p3d->SetScale(m_Transform.scaling.x(), m_Transform.scaling.y(), m_Transform.scaling.z()); + p3d->SetUserMatrix(nullptr); } } @@ -203,6 +199,7 @@ public: } if (root) { + // Now that we use internal TRS, the prop's total matrix is GetMatrix() m_HighlightActor->SetUserMatrix(root->GetMatrix()); } @@ -227,6 +224,15 @@ public: + + + + + + + + + Puppet::Puppet() : Object(), pd(new PuppetData) { ULIB_ACTIVATE_DISPLAY_PROPERTIES; for (auto* p : this->GetDisplayProperties()) { @@ -278,6 +284,16 @@ void Puppet::RemoveProp(vtkProp *prop) // TODO } +void Puppet::ApplyAppearance(vtkProp* prop) +{ + pd->ApplyAppearance(prop); +} + +void Puppet::ApplyTransform(vtkProp3D* p3d) +{ + pd->ApplyTransform(p3d); +} + vtkPropCollection *Puppet::GetParts() { @@ -485,7 +501,7 @@ void Puppet::SetSelected(bool selected) if (!pd->m_Selectable) return; if (pd->m_Selected == selected) return; pd->m_Selected = selected; - pd->UpdateHighlight();0 + pd->UpdateHighlight(); } bool Puppet::IsSelected() const @@ -497,31 +513,15 @@ void Puppet::Update() { vtkProp* root = this->GetProp(); if (root) { - pd->ApplyAppearance(root); - // Handle transformation synchronization from content if (auto* content = dynamic_cast(GetContent())) { - pd->m_Position = content->GetPosition().cast(); - pd->m_Orientation = content->GetOrientation().cast(); - pd->m_Scale = content->GetScale().cast(); + pd->m_Transform = *content; // Uses TRS(const AffineTransform&) + } - if (auto* p3d = vtkProp3D::SafeDownCast(root)) { - vtkNew vmat; - const Matrix4f& emat = content->GetMatrix(); - for(int i=0; i<4; ++i) for(int j=0; j<4; ++j) vmat->SetElement(i, j, emat(i,j)); - p3d->SetUserMatrix(vmat); - - // Clear base transform to avoid double-application - p3d->SetPosition(0,0,0); - p3d->SetOrientation(0,0,0); - p3d->SetScale(1,1,1); - } - } - else 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 (auto* p3d = vtkProp3D::SafeDownCast(root)) { + pd->ApplyTransform(p3d); } + pd->ApplyAppearance(root); } vtkProp3DCollection *props = pd->m_Assembly->GetParts(); @@ -564,32 +564,29 @@ void Puppet::SyncFromVtk() if (auto* p3d = vtkProp3D::SafeDownCast(root)) { // Handle content synchronization if it's an AffineTransform if (auto* content = dynamic_cast(GetContent())) { - vtkMatrix4x4* vmat = p3d->GetUserMatrix(); - if (vmat) { - Matrix4f emat; - for (int i=0; i<4; ++i) - for (int j=0; j<4; ++j) - emat(i, j) = vmat->GetElement(i, j); - content->SetMatrix(emat); - - // Re-sync internal puppet properties from the now-updated content - pd->m_Position = content->GetPosition().cast(); - pd->m_Orientation = content->GetOrientation().cast(); - pd->m_Scale = content->GetScale().cast(); - } - } - else { - // Update internal puppet properties directly from base components - // only if no content exists (old behavior) double pos[3], ori[3], scale[3]; p3d->GetPosition(pos); p3d->GetOrientation(ori); p3d->GetScale(scale); - 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]; - } + + // Convert VTK Degrees to Model Radians + content->SetPosition(Vector3f(pos[0], pos[1], pos[2])); + content->SetOrientation(Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree); + content->SetScale(Vector3f(scale[0], scale[1], scale[2])); + + // Re-sync internal puppet properties from the now-updated content + pd->m_Transform = *content; + } + else { + // Update internal puppet TRS directly from VTK components + double pos[3], ori[3], scale[3]; + p3d->GetPosition(pos); + p3d->GetOrientation(ori); + p3d->GetScale(scale); + pd->m_Transform.position = Vector3f(pos[0], pos[1], pos[2]); + // Convert VTK Degrees to internal Radians + pd->m_Transform.rotation = Vector3f(ori[0], ori[1], ori[2]) * CLHEP::degree; + pd->m_Transform.scaling = Vector3f(scale[0], scale[1], scale[2]); } // Notify puppet properties updated @@ -609,9 +606,7 @@ struct TransformProxy { PuppetData* pd; template 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, ""); + ar & boost::serialization::make_nvp("Transform", pd->m_Transform); } }; diff --git a/src/Vtk/uLibVtkInterface.h b/src/Vtk/uLibVtkInterface.h index d0e7332..74ae5ee 100644 --- a/src/Vtk/uLibVtkInterface.h +++ b/src/Vtk/uLibVtkInterface.h @@ -38,6 +38,7 @@ // vtk classes forward declaration // class vtkProp; +class vtkProp3D; class vtkPolyData; class vtkPropCollection; class vtkRenderer; @@ -121,6 +122,9 @@ protected: void RemoveProp(vtkProp *prop); + void ApplyAppearance(vtkProp* prop); + void ApplyTransform(vtkProp3D* p3d); + std::vector m_DisplayProperties; mutable uLib::RecursiveMutex m_UpdateMutex; diff --git a/src/Vtk/vtkHandlerWidget.cpp b/src/Vtk/vtkHandlerWidget.cpp index 4f460ac..dd7dabc 100644 --- a/src/Vtk/vtkHandlerWidget.cpp +++ b/src/Vtk/vtkHandlerWidget.cpp @@ -48,6 +48,8 @@ #include #include #include +#include "Math/Transform.h" +#include "Vtk/Math/vtkDense.h" namespace uLib { namespace Vtk { @@ -62,20 +64,23 @@ struct HandlerWidgetData { vtkSmartPointer<::vtkActor> m_RotCam; // Camera ring vtkSmartPointer<::vtkActor> m_ScaleX, m_ScaleY, m_ScaleZ; // Cubes + // cut plane to see only half of rotation handles vtkSmartPointer<::vtkPlane> m_ClipPlane; + // picker to select the gizmo vtkSmartPointer<::vtkCellPicker> m_Picker; + + // initial transform of the object vtkSmartPointer<::vtkTransform> m_InitialTransform; - std::vector> m_TransformChain; - vtkSmartPointer<::vtkMatrix4x4> m_BaseMatrix; + // undo stack + std::vector m_UndoStack; 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; } }; @@ -95,7 +100,6 @@ vtkHandlerWidget::vtkHandlerWidget() : d(new HandlerWidgetData()) { this->m_TranslationEnabled = true; this->m_RotationEnabled = true; this->m_ScalingEnabled = true; - d->m_BaseMatrix->Identity(); this->CreateGizmos(); } @@ -108,19 +112,14 @@ vtkHandlerWidget::~vtkHandlerWidget() { return d->m_OverlayRenderer; } + void vtkHandlerWidget::SetProp3D(::vtkProp3D *prop) { if (this->Prop3D == prop) { return; } this->Prop3D = prop; if (this->Prop3D) { - // Initialize d->m_BaseMatrix from the object's current matrix - if (this->Prop3D->GetUserMatrix()) { - this->d->m_BaseMatrix->DeepCopy(this->Prop3D->GetUserMatrix()); - } else { - this->d->m_BaseMatrix->Identity(); - } - this->d->m_TransformChain.clear(); // Clear any previous transform chain + this->d->m_UndoStack.clear(); // Clear history when selecting new object this->UpdateGizmoPosition(); } this->Modified(); @@ -247,20 +246,19 @@ void vtkHandlerWidget::OnKeyPress() { bool ctrl = (this->Interactor->GetControlKey() != 0); if (ctrl && key == "z") { - if (!this->d->m_TransformChain.empty()) { + if (!this->d->m_UndoStack.empty()) { std::cout << "Undoing last transform action..." << std::endl; - this->d->m_TransformChain.pop_back(); + uLib::TRS target = this->d->m_UndoStack.back(); + this->d->m_UndoStack.pop_back(); - // Update object from chain - vtkNew total; - total->PostMultiply(); - total->SetMatrix(this->d->m_BaseMatrix.GetPointer()); - for (auto& t : d->m_TransformChain) { - total->Concatenate(t); - } - - if (this->Prop3D && this->Prop3D->GetUserMatrix()) { - this->Prop3D->GetUserMatrix()->DeepCopy(total->GetMatrix()); + if (this->Prop3D) { + this->Prop3D->SetPosition(target.position.x(), target.position.y(), target.position.z()); + // Convert Model Radians to VTK Degrees + this->Prop3D->SetOrientation(target.rotation.x() / CLHEP::degree, + target.rotation.y() / CLHEP::degree, + target.rotation.z() / CLHEP::degree); + this->Prop3D->SetScale(target.scaling.x(), target.scaling.y(), target.scaling.z()); + this->Prop3D->SetUserMatrix(nullptr); this->Prop3D->Modified(); this->UpdateGizmoPosition(); this->InvokeEvent(::vtkCommand::InteractionEvent, nullptr); @@ -311,21 +309,12 @@ void vtkHandlerWidget::OnLeftButtonDown() { this->StartEventPosition[0] = X; this->StartEventPosition[1] = Y; if (this->Prop3D) { - if (!this->Prop3D->GetUserMatrix()) { - vtkNew vmat; - this->Prop3D->SetUserMatrix(vmat); - } - - // 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 d->m_InitialTransform as the state BEFORE this drag - vtkNew current; - current->PostMultiply(); - current->SetMatrix(this->d->m_BaseMatrix.GetPointer()); - for (auto& t : d->m_TransformChain) { - current->Concatenate(t); - } - this->d->m_InitialTransform->SetMatrix(current->GetMatrix()); + // Capture current state for Undo + this->d->m_UndoStack.push_back(uLib::TRS(uLib::Vtk::VtkToMatrix4f(this->Prop3D->GetMatrix()))); + if (this->d->m_UndoStack.size() > 50) this->d->m_UndoStack.erase(this->d->m_UndoStack.begin()); + + // Use the prop's total matrix for calculation baseline + this->d->m_InitialTransform->SetMatrix(this->Prop3D->GetMatrix()); } this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(::vtkCommand::StartInteractionEvent, nullptr); @@ -337,27 +326,6 @@ void vtkHandlerWidget::OnLeftButtonUp() { if (this->Interaction == IDLE) return; - // Finalize the current interaction into the chain - int X = this->Interactor->GetEventPosition()[0]; - int Y = this->Interactor->GetEventPosition()[1]; - - // 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 d->m_InitialTransform and current UserMatrix. - if (this->Prop3D && this->Prop3D->GetUserMatrix()) { - vtkNew inv; - vtkMatrix4x4::Invert(this->d->m_InitialTransform->GetMatrix(), inv); - - vtkNew final_op_mat; - vtkMatrix4x4::Multiply4x4(this->Prop3D->GetUserMatrix(), inv, final_op_mat); - - vtkNew final_op; - final_op->SetMatrix(final_op_mat); - - this->d->m_TransformChain.push_back(final_op); - std::cout << "Action finalized. Chain size: " << this->d->m_TransformChain.size() << std::endl; - } - this->Interaction = IDLE; this->EventCallbackCommand->SetAbortFlag(1); this->InvokeEvent(::vtkCommand::EndInteractionEvent, nullptr); @@ -578,9 +546,17 @@ void vtkHandlerWidget::OnMouseMove() { total->SetMatrix(this->d->m_InitialTransform->GetMatrix()); // d->m_InitialTransform is already Base*Chain total->Concatenate(op); - vtkMatrix4x4* targetMat = this->Prop3D->GetUserMatrix(); - if (targetMat) { - targetMat->DeepCopy(total->GetMatrix()); + if (this->Prop3D) { + double p[3], r[3], s[3]; + total->GetPosition(p); + total->GetOrientation(r); + total->GetScale(s); + this->Prop3D->SetPosition(p); + // VTK GetOrientation already returned degrees, so r is in degrees. + // We apply it directly back to VTK. + this->Prop3D->SetOrientation(r); + this->Prop3D->SetScale(s); + this->Prop3D->SetUserMatrix(nullptr); } this->Prop3D->Modified(); @@ -671,7 +647,7 @@ void vtkHandlerWidget::SetTransform(::vtkTransform *t) { void vtkHandlerWidget::GetTransform(::vtkTransform *t) { if (!t || !this->Prop3D) return; - t->SetMatrix(this->Prop3D->GetUserMatrix()); + t->SetMatrix(this->Prop3D->GetMatrix()); } void vtkHandlerWidget::CreateGizmos() { diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index d7b610b..f4c2026 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -209,7 +209,6 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren) for (auto* p : self->m_Puppets) { if (p->IsSelected()) { p->SyncFromVtk(); - p->Update(); } } });