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

@@ -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();
}