/*========================================================================= Program: Visualization Toolkit Module: vtkHardwarePicker.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkHardwarePicker.h" #include "vtkAbstractMapper3D.h" #include "vtkAbstractVolumeMapper.h" #include "vtkActor.h" #include "vtkAssemblyPath.h" #include "vtkCamera.h" #include "vtkCommand.h" #include "vtkCompositeDataSet.h" #include "vtkDataArray.h" #include "vtkDataSet.h" #include "vtkIdTypeArray.h" #include "vtkImageMapper3D.h" #include "vtkImageSlice.h" #include "vtkInformation.h" #include "vtkLODProp3D.h" #include "vtkMapper.h" #include "vtkMath.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPolygon.h" #include "vtkPropCollection.h" #include "vtkProperty.h" #include "vtkRenderer.h" #include "vtkSelection.h" #include "vtkSelectionNode.h" #include "vtkTriangle.h" #include "vtkVolume.h" #include namespace { constexpr double DEFAULT_VALUE = std::numeric_limits::quiet_NaN(); constexpr double IntersectionTolerance = 0.0000000001; constexpr double PI_2 = vtkMath::Pi() / 2.0f; } //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkHardwarePicker); //------------------------------------------------------------------------------ vtkHardwarePicker::vtkHardwarePicker() { this->SnapToMeshPoint = false; this->PixelTolerance = 5; this->HardwareSelection = vtkSmartPointer::New(); this->NearRayPoint[0] = DEFAULT_VALUE; this->NearRayPoint[1] = DEFAULT_VALUE; this->NearRayPoint[2] = DEFAULT_VALUE; this->FarRayPoint[0] = DEFAULT_VALUE; this->FarRayPoint[1] = DEFAULT_VALUE; this->FarRayPoint[2] = DEFAULT_VALUE; this->Mapper = nullptr; this->DataSet = nullptr; this->CompositeDataSet = nullptr; this->FlatBlockIndex = -1; this->PointId = -1; this->CellId = -1; this->SubId = -1; this->PCoords[0] = DEFAULT_VALUE; this->PCoords[1] = DEFAULT_VALUE; this->PCoords[2] = DEFAULT_VALUE; this->PickPosition[0] = DEFAULT_VALUE; this->PickPosition[1] = DEFAULT_VALUE; this->PickPosition[2] = DEFAULT_VALUE; this->PickNormal[0] = DEFAULT_VALUE; this->PickNormal[1] = DEFAULT_VALUE; this->PickNormal[2] = DEFAULT_VALUE; this->NormalFlipped = false; } //------------------------------------------------------------------------------ vtkHardwarePicker::~vtkHardwarePicker() = default; //------------------------------------------------------------------------------ void vtkHardwarePicker::Initialize() { this->vtkAbstractPropPicker::Initialize(); this->NearRayPoint[0] = DEFAULT_VALUE; this->NearRayPoint[1] = DEFAULT_VALUE; this->NearRayPoint[2] = DEFAULT_VALUE; this->FarRayPoint[0] = DEFAULT_VALUE; this->FarRayPoint[1] = DEFAULT_VALUE; this->FarRayPoint[2] = DEFAULT_VALUE; this->Mapper = nullptr; this->DataSet = nullptr; this->CompositeDataSet = nullptr; this->FlatBlockIndex = -1; this->PointId = -1; this->CellId = -1; this->SubId = -1; this->PCoords[0] = DEFAULT_VALUE; this->PCoords[1] = DEFAULT_VALUE; this->PCoords[2] = DEFAULT_VALUE; this->PickPosition[0] = DEFAULT_VALUE; this->PickPosition[1] = DEFAULT_VALUE; this->PickPosition[2] = DEFAULT_VALUE; this->PickNormal[0] = DEFAULT_VALUE; this->PickNormal[1] = DEFAULT_VALUE; this->PickNormal[2] = DEFAULT_VALUE; this->NormalFlipped = false; } //------------------------------------------------------------------------------ int vtkHardwarePicker::TypeDecipher(vtkProp* propCandidate, vtkAbstractMapper3D** mapper) { int pickable = 0; *mapper = nullptr; vtkActor* actor; vtkLODProp3D* prop3D; vtkProperty* tempProperty; vtkVolume* volume; vtkImageSlice* imageSlice; if (propCandidate->GetPickable() && propCandidate->GetVisibility()) { pickable = 1; if ((actor = vtkActor::SafeDownCast(propCandidate)) != nullptr) { *mapper = actor->GetMapper(); if (actor->GetProperty()->GetOpacity() <= 0.0) { pickable = 0; } } else if ((prop3D = vtkLODProp3D::SafeDownCast(propCandidate)) != nullptr) { int LODId = prop3D->GetPickLODID(); *mapper = prop3D->GetLODMapper(LODId); // if the mapper is a vtkMapper (as opposed to a vtkVolumeMapper), // then check the transparency to see if the object is pickable if (vtkMapper::SafeDownCast(*mapper) != nullptr) { prop3D->GetLODProperty(LODId, &tempProperty); if (tempProperty->GetOpacity() <= 0.0) { pickable = 0; } } } else if ((volume = vtkVolume::SafeDownCast(propCandidate)) != nullptr) { *mapper = volume->GetMapper(); } else if ((imageSlice = vtkImageSlice::SafeDownCast(propCandidate)) != nullptr) { *mapper = imageSlice->GetMapper(); } else { pickable = 0; // only vtkProp3D's (actors and volumes) can be picked } } return pickable; } //---------------------------------------------------------------------------- void vtkHardwarePicker::FixNormalSign() { vtkCamera* camera = this->Renderer->GetActiveCamera(); double vpn[3]; camera->GetViewPlaneNormal(vpn); if (vtkMath::AngleBetweenVectors(this->PickNormal, vpn) > PI_2) { this->PickNormal[0] *= -1; this->PickNormal[1] *= -1; this->PickNormal[2] *= -1; this->NormalFlipped = true; } } //---------------------------------------------------------------------------- int vtkHardwarePicker::ComputeSurfaceNormal(vtkDataSet* data, vtkCell* cell, double* weights) { vtkDataArray* normals = data->GetPointData()->GetNormals(); if (normals) { this->PickNormal[0] = this->PickNormal[1] = this->PickNormal[2] = 0.0; double pointNormal[3]; const vtkIdType numPoints = cell->GetNumberOfPoints(); for (vtkIdType k = 0; k < numPoints; k++) { normals->GetTuple(cell->PointIds->GetId(k), pointNormal); this->PickNormal[0] += pointNormal[0] * weights[k]; this->PickNormal[1] += pointNormal[1] * weights[k]; this->PickNormal[2] += pointNormal[2] * weights[k]; } vtkMath::Normalize(this->PickNormal); } else { if (cell->GetCellDimension() == 3) { double t; int subId; double pcoord[3], x[3]; int closestIntersectedFaceId = -1; double minDist2 = VTK_DOUBLE_MAX; // find the face that the ray intersected with that is closer to the intersection point for (int i = 0; i < cell->GetNumberOfFaces(); ++i) { if (cell->GetFace(i)->IntersectWithLine(this->NearRayPoint, this->FarRayPoint, IntersectionTolerance, t, x, pcoord, subId) != 0 && t != VTK_DOUBLE_MAX) { double dist2 = vtkMath::Distance2BetweenPoints(x, this->PickPosition); if (dist2 < minDist2) { minDist2 = dist2; closestIntersectedFaceId = i; } } } // calculate the normal of the 2D face vtkPolygon::ComputeNormal(cell->GetFace(closestIntersectedFaceId)->Points, this->PickNormal); this->FixNormalSign(); } else if (cell->GetCellDimension() == 2) { if (cell->GetCellType() != VTK_TRIANGLE_STRIP) { // calculate the normal of the 2D cell vtkPolygon::ComputeNormal(cell->Points, this->PickNormal); this->FixNormalSign(); } else // cell->GetCellType() == VTK_TRIANGLE_STRIP { static int idx[2][3] = { { 0, 1, 2 }, { 1, 0, 2 } }; int* order = idx[this->SubId & 1]; vtkIdType pointIds[3]; double points[3][3]; pointIds[0] = cell->PointIds->GetId(this->SubId + order[0]); pointIds[1] = cell->PointIds->GetId(this->SubId + order[1]); pointIds[2] = cell->PointIds->GetId(this->SubId + order[2]); data->GetPoint(pointIds[0], points[0]); data->GetPoint(pointIds[1], points[1]); data->GetPoint(pointIds[2], points[2]); // calculate the normal of the subId triangle of the triangle strip cell vtkTriangle::ComputeNormal(points[0], points[1], points[2], this->PickNormal); this->FixNormalSign(); } } else { return 0; } } return 1; } //---------------------------------------------------------------------------- void vtkHardwarePicker::ComputeIntersectionFromDataSet(vtkDataSet* ds) { if (this->SnapToMeshPoint) // if we are snapping { ds->GetPoint(this->PointId, this->PickPosition); vtkDataArray* normals = ds->GetPointData()->GetNormals(); if (normals != nullptr) // if point normals exist { normals->GetTuple(this->PointId, this->PickNormal); } else { this->PickNormal[0] = this->PickNormal[1] = this->PickNormal[2] = DEFAULT_VALUE; } } else // if we are not snapping { double t, x[3]; vtkCell* cell = ds->GetCell(this->CellId); int intersection = cell->IntersectWithLine(this->NearRayPoint, this->FarRayPoint, IntersectionTolerance, t, this->PickPosition, this->PCoords, this->SubId); if (intersection == 0 && t == VTK_DOUBLE_MAX) { this->PickPosition[0] = this->PickPosition[1] = this->PickPosition[2] = DEFAULT_VALUE; this->PickNormal[0] = this->PickNormal[1] = this->PickNormal[2] = DEFAULT_VALUE; vtkErrorMacro("The intersection was not properly found"); return; } std::vector weights; weights.resize(cell->GetNumberOfPoints()); cell->EvaluateLocation(this->SubId, this->PCoords, x, weights.data()); if (!vtkHardwarePicker::ComputeSurfaceNormal(ds, cell, weights.data())) { this->PickNormal[0] = this->PickNormal[1] = this->PickNormal[2] = DEFAULT_VALUE; } } } //------------------------------------------------------------------------------ int vtkHardwarePicker::Pick( double selectionX, double selectionY, double vtkNotUsed(selectionZ), vtkRenderer* renderer) { // initialize picking process this->Initialize(); this->Renderer = renderer; this->SelectionPoint[0] = selectionX; this->SelectionPoint[1] = selectionY; this->SelectionPoint[2] = 0; // invoke start pick method if defined this->InvokeEvent(vtkCommand::StartPickEvent, nullptr); // choose which prop collection to use vtkPropCollection* props = this->PickFromList ? this->PickList : this->Renderer->GetViewProps(); // preserve only the pickable props. this->PickableProps->RemoveAllItems(); if (props->GetNumberOfItems() > 0) { vtkCollectionSimpleIterator pit; vtkProp* prop; for (props->InitTraversal(pit); (prop = props->GetNextProp(pit));) { vtkAssemblyPath* path; for (prop->InitPathTraversal(); (path = prop->GetNextPath());) { vtkProp* propCandidate = path->GetLastNode()->GetViewProp(); vtkAbstractMapper3D* mapper = nullptr; int pickable = this->TypeDecipher(propCandidate, &mapper); if (pickable) { this->PickableProps->AddItem(propCandidate); } } } } if (this->SnapToMeshPoint) // snap to the closest point { // define the point picking area double area[4] = { selectionX - this->PixelTolerance, selectionY - this->PixelTolerance, selectionX + this->PixelTolerance, selectionY + this->PixelTolerance }; // do the hardware point pick in the given area this->SetPath(renderer->PickPropFrom(area[0], area[1], area[2], area[3], this->PickableProps, vtkDataObject::FIELD_ASSOCIATION_POINTS, this->HardwareSelection)); } else // pick a cell { // do the hardware cell pick this->SetPath(renderer->PickPropFrom(selectionX, selectionY, this->PickableProps, vtkDataObject::FIELD_ASSOCIATION_CELLS, this->HardwareSelection)); } if (this->Path) // if there was a pick { vtkProp* propCandidate = this->Path->GetLastNode()->GetViewProp(); vtkAbstractMapper3D* mapper = nullptr; // find the mapper and dataset corresponding to the picked prop int pickable = this->TypeDecipher(propCandidate, &mapper); if (pickable) { if (mapper) { this->Mapper = mapper; vtkMapper* map1; vtkAbstractVolumeMapper* vmap; vtkImageMapper3D* imap; if ((map1 = vtkMapper::SafeDownCast(mapper)) != nullptr) { this->Mapper = map1; this->DataSet = map1->GetInput(); this->CompositeDataSet = vtkCompositeDataSet::SafeDownCast(map1->GetInputDataObject(0, 0)); } else if ((vmap = vtkAbstractVolumeMapper::SafeDownCast(mapper)) != nullptr) { this->Mapper = vmap; this->DataSet = vmap->GetDataSetInput(); this->CompositeDataSet = vtkCompositeDataSet::SafeDownCast(vmap->GetInputDataObject(0, 0)); } else if ((imap = vtkImageMapper3D::SafeDownCast(mapper)) != nullptr) { this->Mapper = imap; this->DataSet = imap->GetDataSetInput(); this->CompositeDataSet = vtkCompositeDataSet::SafeDownCast(imap->GetInputDataObject(0, 0)); } else { this->DataSet = nullptr; this->CompositeDataSet = nullptr; } } } if (this->DataSet || this->CompositeDataSet) { // define FlatBlockIndex if (this->CompositeDataSet) { this->FlatBlockIndex = this->HardwareSelection->GetNode(0)->GetProperties()->Get( vtkSelectionNode::COMPOSITE_INDEX()); } // define selected dataset vtkDataSet* selectedDataSet = this->DataSet ? this->DataSet : (this->CompositeDataSet ? (vtkDataSet::SafeDownCast(this->CompositeDataSet->GetDataSet( static_cast(this->FlatBlockIndex)))) : nullptr); // define PointId/CellId vtkIdType selectionId = vtkIdTypeArray::SafeDownCast(this->HardwareSelection->GetNode(0)->GetSelectionList()) ->GetValue(0); // Note: the hardware selection may return a selectionId that does not correspond to a point // or cell in the dataset. If that happens, if we were to use vtkExtractSelection, the // resulted dataset would have 0 points/cells. Hence, we will set the PointId/CellId as -1. if (this->SnapToMeshPoint) { if (selectedDataSet) { this->PointId = selectionId < selectedDataSet->GetNumberOfPoints() ? selectionId : -1; } } else { if (selectedDataSet) { this->CellId = selectionId < selectedDataSet->GetNumberOfCells() ? selectionId : -1; } } // define ray points in display coordinates double nearDisplayPoint[3] = { selectionX, selectionY, 0.0 }; double farDisplayPoint[3] = { selectionX, selectionY, 1.0 }; // compute near ray point in world coordinates this->Renderer->SetDisplayPoint(nearDisplayPoint); this->Renderer->DisplayToWorld(); const double* world = this->Renderer->GetWorldPoint(); for (int i = 0; i < 3; i++) { this->NearRayPoint[i] = world[i] / world[3]; } // compute far line point in world coordinates renderer->SetDisplayPoint(farDisplayPoint); renderer->DisplayToWorld(); world = renderer->GetWorldPoint(); for (int i = 0; i < 3; i++) { this->FarRayPoint[i] = world[i] / world[3]; } if (this->PointId != -1 || this->CellId != -1) { if (selectedDataSet) { this->ComputeIntersectionFromDataSet(selectedDataSet); } } } else { vtkErrorMacro("Failed to find a dataset corresponding to the picked prop."); } } else // since a path was not found, return camera focal point and plane normal { vtkCamera* camera = this->Renderer->GetActiveCamera(); double cameraFP[4]; camera->GetFocalPoint(cameraFP); cameraFP[3] = 1.0; this->Renderer->SetWorldPoint(cameraFP); this->Renderer->WorldToDisplay(); double* displayCoord = this->Renderer->GetDisplayPoint(); // Handle display to world conversion double display[3] = { (double)selectionX, (double)selectionY, displayCoord[2] }; this->Renderer->SetDisplayPoint(display); this->Renderer->DisplayToWorld(); const double* world = this->Renderer->GetWorldPoint(); // define PickPosition and PickNormal for (int i = 0; i < 3; i++) { this->PickPosition[i] = world[i] / world[3]; } camera->GetViewPlaneNormal(this->PickNormal); } int picked = 0; if (this->Path) { // invoke pick method if one defined - prop goes first this->Path->GetFirstNode()->GetViewProp()->Pick(); this->InvokeEvent(vtkCommand::PickEvent, nullptr); picked = 1; } // invoke end pick method if defined this->InvokeEvent(vtkCommand::EndPickEvent, nullptr); return picked; } //------------------------------------------------------------------------------ void vtkHardwarePicker::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "SnapToMeshPoint : " << (this->SnapToMeshPoint ? "yes" : "no") << endl; os << indent << "PixelTolerance : " << this->PixelTolerance << endl; os << indent << "NearRayPoint: (" << this->NearRayPoint[0] << "," << this->NearRayPoint[1] << "," << this->NearRayPoint[2] << ")" << endl; os << indent << "FarRayPoint: (" << this->FarRayPoint[0] << ", " << this->FarRayPoint[1] << ", " << this->FarRayPoint[2] << ")" << endl; if (this->Mapper) { os << indent << "Mapper: " << this->Mapper << endl; } else { os << indent << "Mapper: (none)" << endl; } if (this->DataSet) { os << indent << "DataSet: " << this->DataSet << endl; } else { os << indent << "DataSet: (none)" << endl; } if (this->CompositeDataSet) { os << indent << "CompositeDataSet: " << this->CompositeDataSet << endl; } else { os << indent << "CompositeDataSet: (none)" << endl; } if (this->FlatBlockIndex > -1) { os << indent << "FlatBlockIndex: " << this->FlatBlockIndex << "\n"; } else { os << indent << "FlatBlockIndex: (none)\n"; } os << indent << "PointId : " << this->PointId << endl; os << indent << "CellId : " << this->CellId << endl; os << indent << "SubId : " << this->SubId << endl; os << indent << "PickNormal: (" << this->PickNormal[0] << "," << this->PickNormal[1] << "," << this->PickNormal[2] << ")" << endl; os << indent << "PCoords: (" << this->PCoords[0] << ", " << this->PCoords[1] << ", " << this->PCoords[2] << ")" << endl; }