// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkCameraOrientationWidget.h" #include "vtkAbstractWidget.h" #include "vtkCallbackCommand.h" #include "vtkCamera.h" #include "vtkCameraInterpolator.h" #include "vtkCameraOrientationRepresentation.h" #include "vtkCommand.h" #include "vtkEvent.h" #include "vtkObject.h" #include "vtkObjectFactory.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" #include "vtkTransform.h" #include "vtkWidgetCallbackMapper.h" #include "vtkWidgetEvent.h" //---------------------------------------------------------------------------- VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkCameraOrientationWidget); //---------------------------------------------------------------------------- vtkCameraOrientationWidget::vtkCameraOrientationWidget() { // Define widget events. this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent, vtkEvent::NoModifier, 0, 0, nullptr, vtkWidgetEvent::Select, this, vtkCameraOrientationWidget::SelectAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent, vtkEvent::NoModifier, 0, 0, nullptr, vtkWidgetEvent::EndSelect, this, vtkCameraOrientationWidget::EndSelectAction); this->CallbackMapper->SetCallbackMethod(vtkCommand::MouseMoveEvent, vtkEvent::NoModifier, 0, 0, nullptr, vtkWidgetEvent::Rotate, this, vtkCameraOrientationWidget::MoveAction); this->CameraInterpolator->SetInterpolationTypeToSpline(); // Initialize a default renderer. vtkNew renderer; renderer->SetViewport(0.8, 0.8, 1.0, 1.0); renderer->GetActiveCamera()->ParallelProjectionOff(); renderer->GetActiveCamera()->Dolly(0.25); renderer->InteractiveOff(); renderer->SetLayer(1); this->SetDefaultRenderer(renderer); } //---------------------------------------------------------------------------- vtkCameraOrientationWidget::~vtkCameraOrientationWidget() = default; //------------------------------------------------------------------------------ vtkRenderer* vtkCameraOrientationWidget::GetParentRenderer() { return this->ParentRenderer; } //------------------------------------------------------------------------------ void vtkCameraOrientationWidget::SetDefaultRenderer(vtkRenderer* renderer) { if (renderer == this->DefaultRenderer) { return; } // remove reorientation observer if (this->DefaultRenderer != nullptr) { this->DefaultRenderer->RemoveObserver(this->ReorientObserverTag); } const bool reEnable = this->Enabled; if (this->Enabled) { // remove previous default renderer from render window. if (this->Interactor) { this->Interactor->GetRenderWindow()->RemoveRenderer(this->DefaultRenderer); } this->SetEnabled(false); } // install observer to sync camera widget orientation with that of parent renderer's camera this->ReorientObserverTag = renderer->AddObserver( vtkCommand::StartEvent, this, &vtkCameraOrientationWidget::OrientWidgetRepresentation); this->Superclass::SetDefaultRenderer(renderer); if (reEnable) { this->SetEnabled(true); if (this->Interactor) { this->Interactor->GetRenderWindow()->AddRenderer(this->DefaultRenderer); } } } //------------------------------------------------------------------------------ void vtkCameraOrientationWidget::SetParentRenderer(vtkRenderer* parentRen) { if (this->ParentRenderer == parentRen) { return; } // detach from previous parent renderer if (this->ParentRenderer != nullptr) { auto renWin = this->ParentRenderer->GetRenderWindow(); if (renWin != nullptr) { if (renWin->HasRenderer(this->DefaultRenderer)) { renWin->RemoveRenderer(this->DefaultRenderer); } const int& numLayers = renWin->GetNumberOfLayers(); renWin->SetNumberOfLayers(numLayers - 1); renWin->RemoveObserver(this->ResizeObserverTag); } } // attach to given parent. if (parentRen != nullptr) { auto renWin = parentRen->GetRenderWindow(); if (renWin != nullptr) { if (!renWin->HasRenderer(this->DefaultRenderer)) { renWin->AddRenderer(this->DefaultRenderer); } this->SetInteractor(renWin->GetInteractor()); const int& numLayers = renWin->GetNumberOfLayers(); renWin->SetNumberOfLayers(numLayers + 1); // In order to occupy sufficient space as per the padding and size of the representation, // the widget always invokes the SquareResize callback at the beginning of every frame. // We do it like that because the viewport (xmin,xmax, ymin, ymax) of the DefaultRenderer // may be different than the previously computed values. Otherwise, in a // serialization/deserialization setup, the viewport values could revert back since a resize // event is never triggered upon deserialization. This approach is acceptable since the // SquareResize method is quite efficient. this->ResizeObserverTag = renWin->AddObserver( vtkCommand::StartEvent, this, &vtkCameraOrientationWidget::SquareResize); } } // assign this->ParentRenderer = parentRen; this->Modified(); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::SetRepresentation(vtkCameraOrientationRepresentation* r) { this->Superclass::SetWidgetRepresentation(r); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::CreateDefaultRepresentation() { if (!this->WidgetRep) { this->WidgetRep = vtkCameraOrientationRepresentation::New(); } } void vtkCameraOrientationWidget::ComputeWidgetState(int X, int Y, int modify /* =0*/) { auto rep = vtkCameraOrientationRepresentation::SafeDownCast(this->WidgetRep); if (rep == nullptr) { return; } // Compute and get representation's interaction state. this->WidgetRep->ComputeInteractionState(X, Y, modify); const auto& interactionState = rep->GetInteractionStateAsEnum(); // Synchronize widget state with representation. if (interactionState == vtkCameraOrientationRepresentation::InteractionStateType::Outside) { this->WidgetState = WidgetStateType::Inactive; } else if (interactionState == vtkCameraOrientationRepresentation::InteractionStateType::Hovering) { this->WidgetState = WidgetStateType::Hot; } // Refresh representation to match interaction state. rep->ApplyInteractionState(interactionState); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::SelectAction(vtkAbstractWidget* w) { // cast to ourself vtkCameraOrientationWidget* const self = vtkCameraOrientationWidget::SafeDownCast(w); if (self == nullptr) { return; } // can only be selected if already hot. if (self->WidgetState != WidgetStateType::Hot) { return; } // Get event position. const int& X = self->Interactor->GetEventPosition()[0]; const int& Y = self->Interactor->GetEventPosition()[1]; // Begin widget interaction. double e[2]; e[0] = static_cast(X); e[1] = static_cast(Y); // we're now activated self->WidgetState = WidgetStateType::Active; // this captures the event position. self->WidgetRep->StartWidgetInteraction(e); self->GrabFocus(self->EventCallbackCommand); self->EventCallbackCommand->AbortFlagOn(); self->StartInteraction(); self->InvokeEvent(vtkCommand::StartInteractionEvent); self->Render(); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::EndSelectAction(vtkAbstractWidget* w) { // cast to ourself vtkCameraOrientationWidget* const self = vtkCameraOrientationWidget::SafeDownCast(w); if (self == nullptr) { return; } auto rep = vtkCameraOrientationRepresentation::SafeDownCast(self->WidgetRep); if (rep == nullptr) { return; } // can only be deselected if already selected if (self->WidgetState != WidgetStateType::Active) { return; } if (self->ParentRenderer == nullptr) { return; } // get event position. const int& X = self->Interactor->GetEventPosition()[0]; const int& Y = self->Interactor->GetEventPosition()[1]; // end widget interaction. double e[2]; e[0] = static_cast(X); e[1] = static_cast(Y); self->WidgetRep->EndWidgetInteraction(e); // deactivate widget. self->WidgetState = WidgetStateType::Inactive; // synchronize orientations if (rep->IsAnyHandleSelected() && (rep->GetInteractionStateAsEnum() == vtkCameraOrientationRepresentation::InteractionStateType::Hovering)) { double back[3], up[3]; rep->GetBack(back); rep->GetUp(up); self->OrientParentCamera(back, up); // this fires off animation if needed if (self->Animate && self->AnimationTimerObserverTag == -1) { // update gizmo and camera to new orientation step by step. self->StartAnimation(); return; } else { if (self->ShouldResetCamera) { self->ParentRenderer->ResetCamera(); } self->Render(); } } // one might move the mouse out of the widget's interactive area during animation // need to compute state. self->ComputeWidgetState(X, Y, 1); self->ReleaseFocus(); self->EventCallbackCommand->AbortFlagOn(); self->EndInteraction(); self->InvokeEvent(vtkCommand::EndInteractionEvent); self->Render(); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::StartAnimation() { this->AnimatorCurrentFrame = 1; this->AnimationTimerId = this->Interactor->CreateRepeatingTimer(1); this->AnimationTimerObserverTag = this->Interactor->AddObserver( vtkCommand::TimerEvent, this, &vtkCameraOrientationWidget::PlayAnimationSingleFrame); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::PlayAnimationSingleFrame( vtkObject*, unsigned long event, void* callData) { if (event == vtkCommand::TimerEvent && (*reinterpret_cast(callData)) == this->AnimationTimerId) { if (this->AnimatorCurrentFrame < this->AnimatorTotalFrames) { this->InterpolateCamera(this->AnimatorCurrentFrame); if (this->ShouldResetCamera) { this->ParentRenderer->ResetCamera(); } this->Render(); this->AnimatorCurrentFrame++; } else { this->StopAnimation(); } } } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::StopAnimation() { if (this->Interactor->DestroyTimer(this->AnimationTimerId)) { this->Interactor->RemoveObserver(this->AnimationTimerObserverTag); this->AnimationTimerObserverTag = -1; // get event position. const int& X = this->Interactor->GetEventPosition()[0]; const int& Y = this->Interactor->GetEventPosition()[1]; // one might have moved the mouse out of the widget's interactive area during animation // need to compute state. this->ComputeWidgetState(X, Y, 1); this->ReleaseFocus(); this->EventCallbackCommand->AbortFlagOn(); this->EndInteraction(); this->InvokeEvent(vtkCommand::EndInteractionEvent); this->Render(); } else { vtkErrorMacro(<< "Failed to stop animation timer " << this->AnimationTimerId); } } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::MoveAction(vtkAbstractWidget* w) { // cast to ourself vtkCameraOrientationWidget* const self = vtkCameraOrientationWidget::SafeDownCast(w); if (self == nullptr) { return; } auto rep = vtkCameraOrientationRepresentation::SafeDownCast(self->WidgetRep); if (rep == nullptr) { return; } // Get event position. const int& X = self->Interactor->GetEventPosition()[0]; const int& Y = self->Interactor->GetEventPosition()[1]; // can only rotate if previously selected, else simply compute widget state. if (self->WidgetState != WidgetStateType::Active) { self->ComputeWidgetState(X, Y, 1); } else // pick handle. { rep->ComputeInteractionState(X, Y, 0); if (self->ParentRenderer == nullptr) { return; } auto cam = self->ParentRenderer->GetActiveCamera(); if (cam == nullptr) { return; } double e[2]; e[0] = static_cast(X); e[1] = static_cast(Y); // compute representation's azimuth, elevation self->WidgetRep->WidgetInteraction(e); // copy widget's az, elev to parent cam. cam->Azimuth(rep->GetAzimuth()); cam->Elevation(rep->GetElevation()); cam->OrthogonalizeViewUp(); self->ParentRenderer->ResetCameraClippingRange(); if (self->Interactor->GetLightFollowCamera()) { self->ParentRenderer->UpdateLightsGeometryToFollowCamera(); } self->EventCallbackCommand->AbortFlagOn(); self->InvokeEvent(vtkCommand::InteractionEvent); } if (self->WidgetState != WidgetStateType::Inactive) { self->Render(); } } //----------------------------------------------------------------------------- void vtkCameraOrientationWidget::OrientParentCamera(double back[3], double up[3]) { if (this->ParentRenderer == nullptr) { return; } vtkCamera* cam = this->ParentRenderer->GetActiveCamera(); this->CameraInterpolator->Initialize(); // get old camera vars double dstPos[3] = {}, srcPos[3] = {}, srcUp[3] = {}, focalP[3] = {}, distV[3] = {}; cam->GetFocalPoint(focalP); cam->GetPosition(srcPos); cam->GetViewUp(srcUp); this->CameraInterpolator->AddCamera(0, cam); // move camera to look down 'back' vtkMath::Subtract(srcPos, focalP, distV); double dist = vtkMath::Norm(distV); for (int i = 0; i < 3; ++i) { dstPos[i] = focalP[i] - back[i] * dist; } // set new camera vars cam->SetFocalPoint(focalP); cam->SetPosition(dstPos); cam->SetViewUp(up); cam->ComputeViewPlaneNormal(); this->CameraInterpolator->AddCamera(this->AnimatorTotalFrames - 1, cam); } //----------------------------------------------------------------------------- void vtkCameraOrientationWidget::OrientWidgetRepresentation() { if (this->ParentRenderer == nullptr) { return; } auto rep = vtkCameraOrientationRepresentation::SafeDownCast(this->WidgetRep); if (rep == nullptr) { return; } vtkCamera* cam = this->ParentRenderer->GetActiveCamera(); if (cam != nullptr) { const double* orient = cam->GetOrientationWXYZ(); const double& angle = orient[0]; const double* axis = orient + 1; rep->GetTransform()->Identity(); rep->GetTransform()->RotateWXYZ(angle, axis); } } //----------------------------------------------------------------------------- void vtkCameraOrientationWidget::InterpolateCamera(int t) { if (this->ParentRenderer == nullptr) { return; } vtkCamera* cam = this->ParentRenderer->GetActiveCamera(); if (cam == nullptr) { return; } this->CameraInterpolator->InterpolateCamera(t, cam); cam->OrthogonalizeViewUp(); // the interpolation results in invalid view up, sometimes .. cam->ComputeViewPlaneNormal(); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::SquareResize() { if (this->DefaultRenderer == nullptr) { return; } auto renWin = this->DefaultRenderer->GetRenderWindow(); if (renWin == nullptr) { return; } auto rep = vtkCameraOrientationRepresentation::SafeDownCast(this->WidgetRep); if (rep == nullptr) { return; } const int* const size = rep->GetSize(); const int maxSz = (size[0] > size[1]) ? size[0] : size[1]; const int* const padding = rep->GetPadding(); const auto& anchoredTo = rep->GetAnchorPosition(); double xmin = 0., xmax = 0., ymin = 0., ymax = 0.; // vp: ViewPort | pad: Padding | w: width | h: height const double vpw = static_cast(maxSz) / renWin->GetActualSize()[0]; const double vph = static_cast(maxSz) / renWin->GetActualSize()[1]; const double vppadw = static_cast(padding[0]) / renWin->GetActualSize()[0]; const double vppadh = static_cast(padding[1]) / renWin->GetActualSize()[1]; switch (anchoredTo) { case vtkCameraOrientationRepresentation::AnchorType::LowerLeft: xmin = 0. + vppadw; xmax = vpw + vppadw; ymin = 0. + vppadh; ymax = vph + vppadh; break; case vtkCameraOrientationRepresentation::AnchorType::LowerRight: xmin = 1. - vpw - vppadw; xmax = 1. - vppadw; ymin = 0. + vppadh; ymax = vph + vppadh; break; case vtkCameraOrientationRepresentation::AnchorType::UpperLeft: xmin = 0.0 + vppadw; xmax = vpw + vppadw; ymin = 1. - vph - vppadh; ymax = 1. - vppadh; break; case vtkCameraOrientationRepresentation::AnchorType::UpperRight: xmin = 1. - vpw - vppadw; xmax = 1. - vppadw; ymin = 1. - vph - vppadh; ymax = 1. - vppadh; break; default: break; } this->DefaultRenderer->SetViewport(xmin, ymin, xmax, ymax); } //---------------------------------------------------------------------------- void vtkCameraOrientationWidget::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); switch (this->WidgetState) { case WidgetStateType::Inactive: os << indent << "Inactive" << '\n'; break; case WidgetStateType::Hot: os << indent << "Hot" << '\n'; break; case WidgetStateType::Active: os << indent << "Active" << '\n'; break; default: break; } os << indent << "ParentRenderer: "; if (this->ParentRenderer != nullptr) { os << this->ParentRenderer->GetObjectDescription() << '\n'; this->ParentRenderer->PrintSelf(os, indent.GetNextIndent()); } else { os << "(null)\n"; } os << indent << "CameraInterpolator:" << this->CameraInterpolator->GetObjectDescription() << '\n'; this->CameraInterpolator->PrintSelf(os, indent.GetNextIndent()); os << indent << "Animate: " << (this->Animate ? "True" : "False") << '\n'; os << indent << "AnimatorTotalFrames: " << this->AnimatorTotalFrames << '\n'; } VTK_ABI_NAMESPACE_END