// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtk3DCursorRepresentation.h" #include "vtkCollection.h" #include "vtkCompositePolyDataMapper.h" #include "vtkCursor3D.h" #include "vtkHardwarePicker.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPolyDataMapper.h" #include "vtkProperty.h" #include "vtkRenderer.h" #include "vtkSphereSource.h" #include "vtkViewport.h" #include #include VTK_ABI_NAMESPACE_BEGIN namespace { // Parameters used when creating cursor shapes double CURSOR_COLOR[3] = { 1.0, 0.0, 0.0 }; constexpr double CROSS_LINE_WIDTH = 2.0; constexpr double SPHERE_RES = 16; } struct vtk3DCursorRepresentation::vtkInternals { vtkInternals(vtk3DCursorRepresentation* parent) : Parent(parent) { } ~vtkInternals() = default; // Update the cursor actor void UpdateCursor(); // Helper methods to create cursor actors vtkSmartPointer CreateCrossCursor(); vtkSmartPointer CreateSphereCursor(); vtk3DCursorRepresentation* Parent = nullptr; vtkSmartPointer Cursor; vtkNew Picker; bool NeedUpdate = true; }; //------------------------------------------------------------------------------ void vtk3DCursorRepresentation::vtkInternals::UpdateCursor() { if (!this->NeedUpdate) { return; } this->NeedUpdate = false; switch (this->Parent->GetShape()) { case CUSTOM_SHAPE: { if (this->Parent->CustomCursor) { this->Cursor = this->Parent->GetCustomCursor(); } break; } case SPHERE_SHAPE: { this->Cursor = this->CreateSphereCursor(); break; } case CROSS_SHAPE: default: { this->Cursor = this->CreateCrossCursor(); break; } } } //------------------------------------------------------------------------------ vtkSmartPointer vtk3DCursorRepresentation::vtkInternals::CreateCrossCursor() { vtkNew cross; cross->AllOff(); cross->AxesOn(); vtkNew mapper; mapper->SetInputConnection(cross->GetOutputPort()); // Disabling it gives better results when zooming close // to the picked actor in the scene mapper->SetResolveCoincidentTopologyToOff(); mapper->Update(); vtkNew cursor; cursor->SetMapper(mapper); cursor->GetProperty()->SetColor(CURSOR_COLOR); cursor->GetProperty()->SetLineWidth(CROSS_LINE_WIDTH); return cursor; } //------------------------------------------------------------------------------ vtkSmartPointer vtk3DCursorRepresentation::vtkInternals::CreateSphereCursor() { vtkNew sphere; sphere->SetThetaResolution(SPHERE_RES); sphere->SetPhiResolution(SPHERE_RES); vtkNew mapper; mapper->SetInputConnection(sphere->GetOutputPort()); // Disabling it gives better results when zooming close // to the picked actor in the scene mapper->SetResolveCoincidentTopologyToOff(); mapper->Update(); vtkNew cursor; cursor->SetMapper(mapper); cursor->GetProperty()->SetColor(CURSOR_COLOR); return cursor; } //------------------------------------------------------------------------------ vtkStandardNewMacro(vtk3DCursorRepresentation); //------------------------------------------------------------------------------ vtk3DCursorRepresentation::vtk3DCursorRepresentation() : Internals(new vtk3DCursorRepresentation::vtkInternals(this)) { this->Internals->UpdateCursor(); this->HandleSize = 15; this->ValidPick = true; } //------------------------------------------------------------------------------ vtk3DCursorRepresentation::~vtk3DCursorRepresentation() = default; //------------------------------------------------------------------------------ void vtk3DCursorRepresentation::WidgetInteraction(double newEventPos[2]) { if (!this->Renderer) { return; } vtkCollectionSimpleIterator cookie; vtkActorCollection* actorcol = this->Renderer->GetActors(); vtkActor* actor; std::map pointArrayNames; std::map cellArrayNames; // Iterate through mappers to disable the potential use of point/cell data // arrays for selection and enforce the use of cell IDs. // This is needed in order to prevent a mismatch between the value retrieved // with hardware picking and the ID of the point/cell we want to extract before // computing the ray intersection (see vtkHardwarePicker). // This typically happen in a ParaView context, where vtkOriginalPoint/CellIds // is generated by the vtkPVGeometryFilter of the representation and used for // the hardware selection by vtkPolyDataMapper. for (actorcol->InitTraversal(cookie); (actor = actorcol->GetNextActor(cookie));) { // the mapper can be a composite polydata mapper or a single OpenGL polydata mapper. if (auto cpdm = vtkCompositePolyDataMapper::SafeDownCast(actor->GetMapper())) { if (cpdm->GetPointIdArrayName()) { pointArrayNames.emplace(cpdm, cpdm->GetPointIdArrayName()); cpdm->SetPointIdArrayName(nullptr); } if (cpdm->GetCellIdArrayName()) { cellArrayNames.emplace(cpdm, cpdm->GetCellIdArrayName()); cpdm->SetCellIdArrayName(nullptr); } } else if (auto glMapper = vtkPolyDataMapper::SafeDownCast(actor->GetMapper())) { if (glMapper->GetPointIdArrayName()) { pointArrayNames.emplace(glMapper, glMapper->GetPointIdArrayName()); glMapper->SetPointIdArrayName(nullptr); } if (glMapper->GetCellIdArrayName()) { cellArrayNames.emplace(glMapper, glMapper->GetCellIdArrayName()); glMapper->SetCellIdArrayName(nullptr); } } } this->Internals->Picker->Pick(newEventPos[0], newEventPos[1], 0.0, this->Renderer); // Restore the original point and cell data arrays after picking for (auto item = pointArrayNames.begin(); item != pointArrayNames.end(); item++) { if (auto cpdm = vtkCompositePolyDataMapper::SafeDownCast(item->first)) { cpdm->SetPointIdArrayName(item->second.c_str()); } else if (auto glMapper = vtkPolyDataMapper::SafeDownCast(item->first)) { glMapper->SetPointIdArrayName(item->second.c_str()); } } for (auto item = cellArrayNames.begin(); item != cellArrayNames.end(); item++) { if (auto cpdm = vtkCompositePolyDataMapper::SafeDownCast(item->first)) { cpdm->SetCellIdArrayName(item->second.c_str()); } else if (auto glMapper = vtkPolyDataMapper::SafeDownCast(item->first)) { glMapper->SetCellIdArrayName(item->second.c_str()); } } double pos[3] = { 0.0 }; this->Internals->Picker->GetPickPosition(pos); this->Internals->Cursor->SetPosition(pos); } //------------------------------------------------------------------------------ void vtk3DCursorRepresentation::BuildRepresentation() { this->Internals->UpdateCursor(); // Target size = HandleSize in world coordinates double cursorPos[3] = { 0.0 }; this->Internals->Cursor->GetPosition(cursorPos); double targetSize = this->SizeHandlesInPixels(1.0, cursorPos); double cursorBounds[6] = { 0.0 }; this->Internals->Cursor->GetBounds(cursorBounds); // Safety check if (cursorBounds[1] - cursorBounds[0] == 0) { return; } const double sizeRatio = 2 * targetSize / (cursorBounds[1] - cursorBounds[0]); // Hardware Picker can return NaN position when the ray cast picking do not find any // intersection due to floating-point arithmetic imprecisions (for example, when hitting // the border of a cell) if (std::isnan(sizeRatio)) { return; } // Rescale the actor to fit the target size double scale[3] = { 0.0 }; this->Internals->Cursor->GetScale(scale); vtkMath::MultiplyScalar(scale, sizeRatio); this->Internals->Cursor->SetScale(scale); } //------------------------------------------------------------------------------ void vtk3DCursorRepresentation::ReleaseGraphicsResources(vtkWindow* win) { this->Internals->Cursor->ReleaseGraphicsResources(win); } //------------------------------------------------------------------------------ int vtk3DCursorRepresentation::RenderOpaqueGeometry(vtkViewport* viewport) { this->BuildRepresentation(); return this->Internals->Cursor->RenderOpaqueGeometry(viewport); } //------------------------------------------------------------------------------ void vtk3DCursorRepresentation::SetCursorShape(int shape) { if (shape < CROSS_SHAPE || shape > CUSTOM_SHAPE) { vtkWarningMacro("Given shape doesn't exist. Valid values are ranging between " << CROSS_SHAPE << " and " << CUSTOM_SHAPE << ".\n Previous cursor shape is preserved."); return; } if (shape != this->Shape) { this->Shape = shape; this->Internals->NeedUpdate = true; } } //------------------------------------------------------------------------------ void vtk3DCursorRepresentation::SetCustomCursor(vtkActor* customCursor) { if (customCursor && customCursor != this->CustomCursor) { this->CustomCursor = customCursor; this->Modified(); if (this->Shape == CUSTOM_SHAPE) { this->Internals->NeedUpdate = true; } } } //------------------------------------------------------------------------------ void vtk3DCursorRepresentation::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); } VTK_ABI_NAMESPACE_END