// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-FileCopyrightText: Copyright (c) Kitware Inc. // SPDX-License-Identifier: BSD-3-Clause AND Apache-2.0 // VTK includes #include "vtkPickingManager.h" #include "vtkAbstractPicker.h" #include "vtkAbstractPropPicker.h" #include "vtkCallbackCommand.h" #include "vtkCamera.h" #include "vtkCommand.h" #include "vtkMath.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkRenderWindow.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" #include "vtkSmartPointer.h" #include "vtkTimeStamp.h" // STL includes #include #include #include #include //------------------------------------------------------------------------------ VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkPickingManager); //------------------------------------------------------------------------------ class vtkPickingManager::vtkInternal { public: vtkInternal(vtkPickingManager* external); ~vtkInternal(); // Callback used to update the current time // of the manager when an event occurs in the RenderWindowInteractor. // Time is used to know if the cached information is still valid or obsolete. static void UpdateTime(vtkObject* caller, unsigned long event, void* clientData, void* callData); // Select the best picker based on various criteria such as z-depth, // 2D overlay and/or distance to picked point. vtkAbstractPicker* SelectPicker(); // Compute the selection. The current implementation use the distance // between the world coordinates of a pick to the camera's ones. vtkAbstractPicker* ComputePickerSelection(double X, double Y, double Z, vtkRenderer* renderer); // Check if a given Observator is associated with a given Picker bool IsObjectLinked(vtkAbstractPicker* picker, vtkObject* object); // Create a new list of associated observers void CreateDefaultCollection(vtkAbstractPicker* picker, vtkObject* object); // vtkCollection doesn't allow nullptr values. Instead we use a vector // containing vtkObject to allow using 0 as a valid value because it is // allowed the return a picker event if he is not associated to a specific // object. // This is related with the capacity when a picker associated with a given // object does not manage others object, // it will automatically be removed from the list as well. typedef std::vector CollectionType; // For code clearance and performances during the computation std::map is // used instead of a vector of pair. Nevertheless, it makes internally use of // vtkSmartPointer and this class does not overload the order operators; // therefore to following functor has to be implemented to keep the data // structure consistent. struct less_smartPtrPicker { bool operator()(const vtkSmartPointer& first, const vtkSmartPointer& second) const { return first < second; } }; typedef std::map, CollectionType, less_smartPtrPicker> PickerObjectsType; typedef std::pair, CollectionType> PickerObjectsPairType; // Associate a given vtkObject to a particular picker. void LinkPickerObject(const PickerObjectsType::iterator& it, vtkObject* object); // Predicate comparing a vtkAbstractPicker* // and a vtkSmartPointer using the PickerObjectsType. // As we use a vtkSmartPointer, this predicate allows to compare the equality // of a pointer on a vtkAbstractPicker with the address contained in // a corresponding vtkSmartPointer. struct equal_smartPtrPicker { equal_smartPtrPicker(vtkAbstractPicker* picker) : Picker(picker) { } bool operator()(const PickerObjectsPairType& pickerObjs) const { return this->Picker == pickerObjs.first; } vtkAbstractPicker* Picker; }; PickerObjectsType Pickers; // Map the picker with the objects vtkTimeStamp CurrentInteractionTime; // Time of the last interaction event vtkTimeStamp LastPickingTime; // Time of the last picking process vtkSmartPointer LastSelectedPicker; // Define callback to keep track of the CurrentTime and the LastPickingTime. // The timeStamp is use to avoid repeating the picking process if the // vtkWindowInteractor has not been modified, it is a huge optimization // avoiding each picker to relaunch the whole mechanisme to determine which // picker has been selected at a state of the rendering. vtkSmartPointer TimerCallback; vtkPickingManager* External; }; //------------------------------------------------------------------------------ // vtkInternal methods //------------------------------------------------------------------------------ vtkPickingManager::vtkInternal::vtkInternal(vtkPickingManager* external) { this->External = external; this->TimerCallback = vtkSmartPointer::New(); this->TimerCallback->SetClientData(this); this->TimerCallback->SetCallback(UpdateTime); } //------------------------------------------------------------------------------ vtkPickingManager::vtkInternal::~vtkInternal() = default; //------------------------------------------------------------------------------ void vtkPickingManager::vtkInternal::CreateDefaultCollection( vtkAbstractPicker* picker, vtkObject* object) { CollectionType objects; objects.push_back(object); this->Pickers.insert(PickerObjectsPairType(picker, objects)); } //------------------------------------------------------------------------------ void vtkPickingManager::vtkInternal::LinkPickerObject( const PickerObjectsType::iterator& it, vtkObject* object) { CollectionType::iterator itObj = std::find(it->second.begin(), it->second.end(), object); if (itObj != it->second.end() && object) { vtkDebugWithObjectMacro(this->External, "vtkPickingtManager::Internal::LinkPickerObject: " << "Current object already linked with the given picker."); return; } it->second.push_back(object); } //------------------------------------------------------------------------------ bool vtkPickingManager::vtkInternal::IsObjectLinked(vtkAbstractPicker* picker, vtkObject* obj) { if (!picker || !obj) { return false; } PickerObjectsType::iterator itPick = std::find_if(this->Pickers.begin(), this->Pickers.end(), equal_smartPtrPicker(picker)); if (itPick == this->Pickers.end()) { return false; } CollectionType::iterator itObj = std::find(itPick->second.begin(), itPick->second.end(), obj); return (itObj != itPick->second.end()); } //------------------------------------------------------------------------------ vtkAbstractPicker* vtkPickingManager::vtkInternal::SelectPicker() { if (!this->External->Interactor) { return nullptr; } else if (this->External->GetOptimizeOnInteractorEvents() && this->CurrentInteractionTime.GetMTime() == this->LastPickingTime) { return this->LastSelectedPicker; } // Get the event position double X = this->External->Interactor->GetEventPosition()[0]; double Y = this->External->Interactor->GetEventPosition()[1]; // Get the poked renderer vtkRenderer* renderer = this->External->Interactor->FindPokedRenderer(static_cast(X), static_cast(Y)); vtkAbstractPicker* selectedPicker = this->ComputePickerSelection(X, Y, 0., renderer); // Keep track of the last picker chosen & last picking time. this->LastSelectedPicker = selectedPicker; this->LastPickingTime = this->CurrentInteractionTime; return selectedPicker; } //------------------------------------------------------------------------------ vtkAbstractPicker* vtkPickingManager::vtkInternal::ComputePickerSelection( double X, double Y, double Z, vtkRenderer* renderer) { vtkAbstractPicker* closestPicker = nullptr; if (!renderer) { return closestPicker; } double* camPos = renderer->GetActiveCamera()->GetPosition(); double smallestDistance2 = std::numeric_limits::max(); for (PickerObjectsType::iterator it = this->Pickers.begin(); it != this->Pickers.end(); ++it) { int pickResult = it->first->Pick(X, Y, Z, renderer); double* pPos = it->first->GetPickPosition(); if (pickResult > 0) // Keep closest object picked. { double distance2 = vtkMath::Distance2BetweenPoints(camPos, pPos); if (smallestDistance2 > distance2) { smallestDistance2 = distance2; closestPicker = it->first; } } } return closestPicker; } //------------------------------------------------------------------------------ void vtkPickingManager::vtkInternal::UpdateTime(vtkObject* vtkNotUsed(caller), unsigned long vtkNotUsed(event), void* clientData, void* vtkNotUsed(calldata)) { vtkPickingManager::vtkInternal* self = reinterpret_cast(clientData); if (!self) { return; } self->CurrentInteractionTime.Modified(); } //------------------------------------------------------------------------------ // vtkPickingManager methods //------------------------------------------------------------------------------ vtkPickingManager::vtkPickingManager() : Interactor(nullptr) , Enabled(false) , OptimizeOnInteractorEvents(true) , Internal(nullptr) { this->Internal = new vtkInternal(this); } //------------------------------------------------------------------------------ vtkPickingManager::~vtkPickingManager() { this->SetInteractor(nullptr); delete this->Internal; } //------------------------------------------------------------------------------ void vtkPickingManager::SetInteractor(vtkRenderWindowInteractor* rwi) { if (rwi == this->Interactor) { return; } if (this->Interactor) { this->Interactor->RemoveObserver(this->Internal->TimerCallback); } this->Interactor = rwi; if (this->Interactor) { this->Interactor->AddObserver(vtkCommand::ModifiedEvent, this->Internal->TimerCallback); } this->Modified(); } //------------------------------------------------------------------------------ void vtkPickingManager::SetOptimizeOnInteractorEvents(bool optimize) { if (this->OptimizeOnInteractorEvents == optimize) { return; } this->OptimizeOnInteractorEvents = optimize; this->Modified(); } //------------------------------------------------------------------------------ void vtkPickingManager::AddPicker(vtkAbstractPicker* picker, vtkObject* object) { if (!picker) { return; } // Link the object if the picker is already registered vtkPickingManager::vtkInternal::PickerObjectsType::iterator it = std::find_if(this->Internal->Pickers.begin(), this->Internal->Pickers.end(), vtkPickingManager::vtkInternal::equal_smartPtrPicker(picker)); if (it != this->Internal->Pickers.end()) { vtkDebugMacro("vtkPickingtManager::AddPicker: " << "Picker already in the manager, the object will be linked"); this->Internal->LinkPickerObject(it, object); return; } // The picker does not exist in the manager yet. // Create the list of associated objects this->Internal->CreateDefaultCollection(picker, object); } //------------------------------------------------------------------------------ void vtkPickingManager::RemovePicker(vtkAbstractPicker* picker, vtkObject* object) { vtkPickingManager::vtkInternal::PickerObjectsType::iterator it = std::find_if(this->Internal->Pickers.begin(), this->Internal->Pickers.end(), vtkPickingManager::vtkInternal::equal_smartPtrPicker(picker)); // The Picker does not exist if (it == this->Internal->Pickers.end()) { return; } vtkPickingManager::vtkInternal::CollectionType::iterator itObj = std::find(it->second.begin(), it->second.end(), object); // The object is not associated with the given picker. if (itObj == it->second.end()) { return; } it->second.erase(itObj); // Delete the picker when it is not associated with any object anymore. if (it->second.empty()) { this->Internal->Pickers.erase(it); } } //------------------------------------------------------------------------------ void vtkPickingManager::RemoveObject(vtkObject* object) { vtkPickingManager::vtkInternal::PickerObjectsType::iterator it = this->Internal->Pickers.begin(); for (; it != this->Internal->Pickers.end();) { vtkPickingManager::vtkInternal::CollectionType::iterator itObj = std::find(it->second.begin(), it->second.end(), object); if (itObj != it->second.end()) { it->second.erase(itObj); if (it->second.empty()) { vtkPickingManager::vtkInternal::PickerObjectsType::iterator toRemove = it; ++it; this->Internal->Pickers.erase(toRemove); continue; } } ++it; } } //------------------------------------------------------------------------------ bool vtkPickingManager::Pick(vtkAbstractPicker* picker, vtkObject* obj) { if (!this->Internal->IsObjectLinked(picker, obj)) { return false; } return (this->Pick(picker)); } //------------------------------------------------------------------------------ bool vtkPickingManager::Pick(vtkObject* obj) { vtkAbstractPicker* picker = this->Internal->SelectPicker(); if (!picker) { return false; } // If the object is not contained in the list of the associated active pickers // return false return (this->Internal->IsObjectLinked(picker, obj)); } //------------------------------------------------------------------------------ bool vtkPickingManager::Pick(vtkAbstractPicker* picker) { return (picker == this->Internal->SelectPicker()); } //------------------------------------------------------------------------------ vtkAssemblyPath* vtkPickingManager::GetAssemblyPath(double X, double Y, double Z, vtkAbstractPropPicker* picker, vtkRenderer* renderer, vtkObject* obj) { if (this->Enabled) { // Return 0 when the Picker is not selected if (!this->Pick(picker, obj)) { return nullptr; } } else { picker->Pick(X, Y, Z, renderer); } return picker->GetPath(); } //------------------------------------------------------------------------------ int vtkPickingManager::GetNumberOfPickers() { return static_cast(this->Internal->Pickers.size()); } //------------------------------------------------------------------------------ int vtkPickingManager::GetNumberOfObjectsLinked(vtkAbstractPicker* picker) { if (!picker) { return 0; } vtkPickingManager::vtkInternal::PickerObjectsType::iterator it = std::find_if(this->Internal->Pickers.begin(), this->Internal->Pickers.end(), vtkPickingManager::vtkInternal::equal_smartPtrPicker(picker)); if (it == this->Internal->Pickers.end()) { return 0; } return static_cast(it->second.size()); } //------------------------------------------------------------------------------ void vtkPickingManager::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "RenderWindowInteractor: " << this->Interactor << "\n"; os << indent << "NumberOfPickers: " << this->Internal->Pickers.size() << "\n"; vtkPickingManager::vtkInternal::PickerObjectsType::iterator it = this->Internal->Pickers.begin(); for (; it != this->Internal->Pickers.end(); ++it) { os << indent << indent << "Picker: " << it->first << "\n"; os << indent << indent << "NumberOfObjectsLinked: " << it->second.size() << "\n"; } } VTK_ABI_NAMESPACE_END