refactor: Simplify vtkDetectorChamber by removing redundant transform management and improve vtkHandlerWidget rotation calculation using ray-plane intersection.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user