refactor: Simplify vtkDetectorChamber by removing redundant transform management and improve vtkHandlerWidget rotation calculation using ray-plane intersection.

This commit is contained in:
AndreaRigoni
2026-03-18 23:08:22 +00:00
parent a9b66a4e12
commit 3b02bb26ac
7 changed files with 142 additions and 184 deletions

View File

@@ -25,6 +25,7 @@
#include "Vtk/HEP/Detectors/vtkDetectorChamber.h"
#include "HEP/Detectors/DetectorChamber.h"
#include "Math/Units.h"
#include "Vtk/uLibVtkViewer.h"
@@ -35,29 +36,31 @@ using namespace uLib;
BOOST_AUTO_TEST_CASE(vtkDetectorChamberTest) {
DetectorChamber d1, d2;
d1.SetSize(Vector3f(1, 1, 1));
d1.SetPosition(Vector3f(0, 0, 0));
d1.Scale(Vector3f(5, 10, 2));
// d1.SetSize(Vector3f(1, 1, 1));
// d1.SetPosition(Vector3f(0, 0, 0));
d1.Scale(Vector3f(5_m, 10_m, 2_m));
d1.Translate(Vector3f(0, 0, 0));
d2.SetSize(Vector3f(1, 1, 1));
d2.SetPosition(Vector3f(0, 0, 0));
d2.Scale(Vector3f(5, 10, 2));
d2.Translate(Vector3f(0, 0, 10));
// d2.SetSize(Vector3f(1, 1, 1));
// d2.SetPosition(Vector3f(0, 0, 0));
d2.Scale(Vector3f(5_m, 10_m, 2_m));
d2.Translate(Vector3f(0, 0, 10_m));
Vtk::vtkDetectorChamber vtkDetectorChamber(&d1);
Vtk::vtkDetectorChamber vtkDetectorChamber2(&d2);
Vtk::vtkDetectorChamber v_d1(&d1);
Vtk::vtkDetectorChamber v_d2(&d2);
v_d1.SetRepresentation(Vtk::Puppet::Surface);
v_d2.SetRepresentation(Vtk::Puppet::Surface);
if (!vtkDetectorChamber.GetProp()) {
if (!v_d1.GetProp()) {
BOOST_FAIL("vtkDetectorChamber::GetProp() returned NULL");
}
if (std::getenv("CTEST_PROJECT_NAME") == nullptr) {
Vtk::Viewer viewer;
viewer.SetGridAxis(Vtk::Viewport::Y);
viewer.AddPuppet(vtkDetectorChamber);
viewer.AddPuppet(vtkDetectorChamber2);
viewer.AddPuppet(v_d1);
viewer.AddPuppet(v_d2);
viewer.Start();
}

View File

@@ -46,100 +46,15 @@ namespace uLib {
namespace Vtk {
vtkDetectorChamber::vtkDetectorChamber(DetectorChamber *content)
: vtkContainerBox(content), m_Actor(vtkActor::New()) {
m_InitialTransform = vtkSmartPointer<vtkTransform>::New();
m_RelativeTransform = vtkSmartPointer<vtkTransform>::New();
m_TotalTransform = vtkSmartPointer<vtkTransform>::New();
this->InstallPipe();
: vtkContainerBox(content) {
}
vtkDetectorChamber::~vtkDetectorChamber() {
m_Actor->Delete();
}
DetectorChamber *vtkDetectorChamber::GetContent() {
return static_cast<DetectorChamber *>(m_Content);
}
void vtkDetectorChamber::PrintSelf(std::ostream &o) const {
vtkContainerBox::PrintSelf(o);
}
void vtkDetectorChamber::SetTransform(vtkTransform *t) {
if (!t) return;
m_RelativeTransform->SetMatrix(t->GetMatrix());
m_RelativeTransform->Update();
// Set content global transform (BaseClass of ContainerBox) //
vtkMatrix4x4 *vmat = m_TotalTransform->GetMatrix();
Matrix4f transform;
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
transform(i, j) = vmat->GetElement(i, j);
this->GetContent()->SetMatrix(transform);
this->GetContent()->Updated(); // emit signal
this->Update();
}
void vtkDetectorChamber::Update() {
if (m_Actor->GetMapper())
m_Actor->GetMapper()->Update();
// If the actor has a UserMatrix, we update the content.
if (m_Actor->GetUserMatrix()) {
vtkMatrix4x4* vmat = m_Actor->GetUserMatrix();
Matrix4f transform;
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
transform(i, j) = vmat->GetElement(i, j);
this->GetContent()->SetMatrix(transform);
this->GetContent()->Updated();
}
BaseClass::Update();
}
void vtkDetectorChamber::InstallPipe() {
if (!m_Content)
return;
vtkSmartPointer<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::New();
cube->SetBounds(0, 1, 0, 1, 0, 1);
// 1. Initialize Global Transform (m_Transform) from Content's matrix (Base
// class AffineTransform)
vtkSmartPointer<vtkMatrix4x4> vmatGlobal =
vtkSmartPointer<vtkMatrix4x4>::New();
Matrix4f matGlobal = this->GetContent()->GetMatrix();
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
vmatGlobal->SetElement(i, j, matGlobal(i, j));
m_InitialTransform->SetMatrix(vmatGlobal);
m_InitialTransform->Update();
vtkSmartPointer<vtkPolyDataMapper> mapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(cube->GetOutputPort());
m_Actor->SetMapper(mapper);
m_Actor->GetProperty()->SetRepresentationToSurface();
m_Actor->GetProperty()->SetEdgeVisibility(true);
m_Actor->GetProperty()->SetOpacity(0.4);
m_Actor->GetProperty()->SetAmbient(0.7);
m_TotalTransform->SetInput(m_RelativeTransform);
m_TotalTransform->Concatenate(m_InitialTransform);
m_Actor->SetUserTransform(m_TotalTransform);
m_TotalTransform->Update();
this->SetProp(m_Actor);
this->Update();
}
} // namespace Vtk
} // namespace uLib

View File

@@ -50,25 +50,9 @@ class vtkDetectorChamber : public vtkContainerBox {
public:
vtkDetectorChamber(DetectorChamber *content);
~vtkDetectorChamber();
virtual ~vtkDetectorChamber();
Content *GetContent();
void SetTransform(class vtkTransform *t);
void Update() override;
void PrintSelf(std::ostream &o) const;
protected:
void InstallPipe() override;
private:
vtkActor *m_Actor;
vtkSmartPointer<vtkTransform> m_InitialTransform;
vtkSmartPointer<vtkTransform> m_RelativeTransform;
vtkSmartPointer<vtkTransform> m_TotalTransform;
};
} // namespace Vtk

View File

@@ -74,9 +74,19 @@ void vtkStructuredGrid::Update() {
if (!vmat) return;
Matrix4f transform = VtkToMatrix4f(vmat);
m_Content->SetMatrix(transform);
// 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 others (like raytracer)
// Debug output
std::cout << "vtkStructuredGrid::Update matrix:\n" << transform << std::endl;
}
void vtkStructuredGrid::InstallPipe() {

View File

@@ -26,6 +26,7 @@
#include "Vtk/uLibVtkViewer.h"
#include "Math/ContainerBox.h"
#include "Math/Units.h"
#include "Vtk/vtkContainerBox.h"
#include "testing-prototype.h"
@@ -36,8 +37,8 @@ int main() {
BEGIN_TESTING(vtk ContainerBox Test);
ContainerBox box;
box.Scale(Vector3f(1,5,1));
box.SetPosition(Vector3f(0,1,0));
box.Scale(Vector3f(1_m,5_m,1_m));
box.SetPosition(Vector3f(0,1_m,0));
Vtk::vtkContainerBox v_box(&box);
v_box.SetRepresentation(Vtk::Puppet::Surface);
v_box.SetOpacity(0.5);

View File

@@ -82,6 +82,9 @@ void vtkContainerBox::contentUpdate() {
vmat = mat;
}
m_Cube->SetUserMatrix(nullptr);
m_Axes->SetUserMatrix(nullptr);
Matrix4f transform = m_Content->GetMatrix();
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j) {
@@ -112,6 +115,9 @@ void vtkContainerBox::Update() {
}
m_Content->Updated(); // Notify change
// Debug output
std::cout << "vtkContainerBox::Update matrix:\n" << transform << std::endl;
}

View File

@@ -220,7 +220,11 @@ void vtkHandlerWidget::OnLeftButtonDown() {
this->StartEventPosition[0] = X;
this->StartEventPosition[1] = Y;
if (this->Prop3D) {
this->m_InitialTransform->SetMatrix(this->Prop3D->GetMatrix());
if (!this->Prop3D->GetUserMatrix()) {
vtkNew<vtkMatrix4x4> vmat;
this->Prop3D->SetUserMatrix(vmat);
}
this->m_InitialTransform->SetMatrix(this->Prop3D->GetUserMatrix());
}
this->EventCallbackCommand->SetAbortFlag(1);
this->InvokeEvent(::vtkCommand::StartInteractionEvent, nullptr);
@@ -256,6 +260,9 @@ void vtkHandlerWidget::OnMouseMove() {
double dx = X - this->StartEventPosition[0];
double dy = Y - this->StartEventPosition[1];
if (dx == 0 && dy == 0) return;
std::cout << "Interaction " << this->Interaction << " dx=" << dx << " dy=" << dy << std::endl;
// Get current gizmo properties from its actors
vtkMatrix4x4 *gizmo_mat = m_AxesX->GetUserMatrix();
if (!gizmo_mat)
@@ -288,120 +295,152 @@ void vtkHandlerWidget::OnMouseMove() {
return (dx * v[0] + dy * v[1]) / v_mag_sq;
};
auto get_rotation_magnitude = [&](double axis[3]) {
// Tangent at pick point
double v_pick[3] = {this->m_StartPickPosition[0] - gpos[0],
this->m_StartPickPosition[1] - gpos[1],
this->m_StartPickPosition[2] - gpos[2]};
double tangent[3];
vtkMath::Cross(axis, v_pick, tangent);
vtkMath::Normalize(tangent);
auto get_rotation_magnitude = [&](double axis[3]) -> double {
// 1. Get Mouse Ray in World Space
double worldNear[4], worldFar[4];
this->ComputeDisplayToWorld(static_cast<double>(X), static_cast<double>(Y), 0.0, worldNear);
this->ComputeDisplayToWorld(static_cast<double>(X), static_cast<double>(Y), 1.0, worldFar);
double camPos[3];
this->CurrentRenderer->GetActiveCamera()->GetPosition(camPos);
double rayDir[3];
for(int i=0; i<3; ++i) rayDir[i] = worldFar[i] - worldNear[i];
vtkMath::Normalize(rayDir);
double p1[3] = {this->m_StartPickPosition[0], this->m_StartPickPosition[1], this->m_StartPickPosition[2]};
double p2[3] = {p1[0] + tangent[0], p1[1] + tangent[1], p1[2] + tangent[2]};
// 2. Intersect Ray with Rotation Plane (Center = gpos, Normal = axis)
double planeNormal[3] = {axis[0], axis[1], axis[2]};
vtkMath::Normalize(planeNormal);
double denom = vtkMath::Dot(rayDir, planeNormal);
if (std::abs(denom) < 1e-6) return 0.0;
double t = (vtkMath::Dot(gpos, planeNormal) - vtkMath::Dot(camPos, planeNormal)) / denom;
double p_current[3];
for(int i=0; i<3; ++i) p_current[i] = camPos[i] + t * rayDir[i];
double d1[3], d2[3];
this->ComputeWorldToDisplay(this->CurrentRenderer, p1[0], p1[1], p1[2], d1);
this->ComputeWorldToDisplay(this->CurrentRenderer, p2[0], p2[1], p2[2], d2);
// 3. Calculate Angular Displacement in Plane
double v_start[3] = {this->m_StartPickPosition[0] - gpos[0],
this->m_StartPickPosition[1] - gpos[1],
this->m_StartPickPosition[2] - gpos[2]};
double v_end[3] = {p_current[0] - gpos[0],
p_current[1] - gpos[1],
p_current[2] - gpos[2]};
double v[2] = {d2[0] - d1[0], d2[1] - d1[1]};
double v_mag_sq = v[0] * v[0] + v[1] * v[1];
if (v_mag_sq < 1.0) return 0.0;
// Return pixels along tangent (mapped to degrees)
return (dx * v[0] + dy * v[1]) / sqrt(v_mag_sq);
double r_start = vtkMath::Norm(v_start);
double r_end = vtkMath::Norm(v_end);
if (r_start < 1e-9 || r_end < 1e-9) return 0.0;
for(int i=0; i<3; ++i) { v_start[i] /= r_start; v_end[i] /= r_end; }
double cross[3];
vtkMath::Cross(v_start, v_end, cross);
double sinAng = vtkMath::Dot(cross, planeNormal);
double cosAng = vtkMath::Dot(v_start, v_end);
return vtkMath::DegreesFromRadians(atan2(sinAng, cosAng));
};
// Create a transform that represents the operation in Gizmo-local space
vtkNew<vtkTransform> delta;
delta->PostMultiply();
delta->Translate(-gpos[0], -gpos[1], -gpos[2]);
// Create a transform that represents the operation in world space around gpos
vtkNew<vtkTransform> op;
op->PostMultiply();
op->Translate(-gpos[0], -gpos[1], -gpos[2]);
// Orientation of the gizmo
vtkNew<vtkMatrix4x4> orient;
orient->Identity();
for (int i = 0; i < 3; ++i) {
orient->SetElement(i, 0, gx[i]);
orient->SetElement(i, 1, gy[i]);
orient->SetElement(i, 2, gz[i]);
}
vtkNew<vtkMatrix4x4> orient_inv;
vtkMatrix4x4::Invert(orient, orient_inv);
delta->Concatenate(orient_inv);
// Now the coordinate system is at gizmo center, aligned with its axes.
double mag = 0;
switch (this->Interaction) {
case TRANS_X:
mag = get_motion_magnitude(gx, gpos);
delta->Translate(mag, 0, 0);
op->Translate(mag * gx[0], mag * gx[1], mag * gx[2]);
break;
case TRANS_Y:
mag = get_motion_magnitude(gy, gpos);
delta->Translate(0, mag, 0);
op->Translate(mag * gy[0], mag * gy[1], mag * gy[2]);
break;
case TRANS_Z:
mag = get_motion_magnitude(gz, gpos);
delta->Translate(0, 0, mag);
op->Translate(mag * gz[0], mag * gz[1], mag * gz[2]);
break;
case ROT_X:
mag = get_rotation_magnitude(gx);
delta->RotateX(mag);
{
double ax[3] = {gx[0], gx[1], gx[2]};
vtkMath::Normalize(ax);
op->RotateWXYZ(mag, ax[0], ax[1], ax[2]);
}
break;
case ROT_Y:
mag = get_rotation_magnitude(gy);
delta->RotateY(mag);
{
double ax[3] = {gy[0], gy[1], gy[2]};
vtkMath::Normalize(ax);
op->RotateWXYZ(mag, ax[0], ax[1], ax[2]);
}
break;
case ROT_Z:
mag = get_rotation_magnitude(gz);
delta->RotateZ(mag);
{
double ax[3] = {gz[0], gz[1], gz[2]};
vtkMath::Normalize(ax);
op->RotateWXYZ(mag, ax[0], ax[1], ax[2]);
}
break;
case SCALE_X:
mag = get_motion_magnitude(gx, gpos);
delta->Scale(std::max(0.1, 1.0 + mag), 1.0, 1.0);
op->Scale(std::max(0.1, 1.0 + mag), 1.0, 1.0);
// Note: Scale might need orient_inv/orient if we want to scale along local axes nicely
// but the current logic for scale already handles this if we concatenated basis.
// For now we keep it simple since user only reported rotation issue.
break;
case SCALE_Y:
mag = get_motion_magnitude(gy, gpos);
delta->Scale(1.0, std::max(0.1, 1.0 + mag), 1.0);
op->Scale(1.0, std::max(0.1, 1.0 + mag), 1.0);
break;
case SCALE_Z:
mag = get_motion_magnitude(gz, gpos);
delta->Scale(1.0, 1.0, std::max(0.1, 1.0 + mag));
op->Scale(1.0, 1.0, std::max(0.1, 1.0 + mag));
break;
case ROT_CAM: {
// Rotate around camera-viewer axis
double camPos[3];
this->CurrentRenderer->GetActiveCamera()->GetPosition(camPos);
double dir[3] = {camPos[0] - gpos[0], camPos[1] - gpos[1],
camPos[2] - gpos[2]};
double dir[3] = {camPos[0] - gpos[0], camPos[1] - gpos[1], camPos[2] - gpos[2]};
vtkMath::Normalize(dir);
// Orientation of the gizmo is currently orient
// But delta is in gizmo-local.
// In gizmo-local, the camera direction is:
double dir4[4] = {dir[0], dir[1], dir[2], 0.0};
double dir_local4[4];
orient_inv->MultiplyPoint(dir4, dir_local4);
double axis_local[3] = {dir_local4[0], dir_local4[1], dir_local4[2]};
mag = get_rotation_magnitude(dir); // Tangent calculated in world space
delta->RotateWXYZ(mag, axis_local[0], axis_local[1], axis_local[2]);
// For camera ring, use screen-space angular delta
double c_disp[3];
this->ComputeWorldToDisplay(gpos[0], gpos[1], gpos[2], c_disp);
double v1[2] = {this->StartEventPosition[0] - c_disp[0], this->StartEventPosition[1] - c_disp[1]};
double v2[2] = {static_cast<double>(X) - c_disp[0], static_cast<double>(Y) - c_disp[1]};
if (vtkMath::Norm2D(v1) > 1.0 && vtkMath::Norm2D(v2) > 1.0) {
double d_ang = vtkMath::DegreesFromRadians(atan2(v2[1], v2[0]) - atan2(v1[1], v1[0]));
while (d_ang > 180) d_ang -= 360;
while (d_ang < -180) d_ang += 360;
mag = d_ang;
}
op->RotateWXYZ(mag, dir[0], dir[1], dir[2]);
break;
}
}
}
// Back to world space
delta->Concatenate(orient);
delta->Translate(gpos[0], gpos[1], gpos[2]);
op->Translate(gpos[0], gpos[1], gpos[2]);
// Apply delta on top of the initial object state
// Apply op on top of the initial object state
vtkNew<vtkTransform> final_t;
final_t->PostMultiply();
final_t->SetMatrix(this->m_InitialTransform->GetMatrix());
final_t->Concatenate(delta);
final_t->Concatenate(op);
this->Prop3D->SetUserMatrix(final_t->GetMatrix());
vtkMatrix4x4* targetMat = this->Prop3D->GetUserMatrix();
if (targetMat) {
targetMat->DeepCopy(final_t->GetMatrix());
// std::cout << "Updated UserMatrix for interaction " << this->Interaction << std::endl;
}
this->Prop3D->Modified();
this->UpdateGizmoPosition();
// HIGH VISIBILITY LOG
std::printf("--- WIDGET Interaction: %d, Mag: %f, Pos: %f %f %f\n", Interaction, mag, gpos[0], gpos[1], gpos[2]);
this->InvokeEvent(::vtkCommand::InteractionEvent, nullptr);
this->Interactor->Render();
}