/*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkPicker.cxx,v $ 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 "vtkPicker.h" #include "vtkActor.h" #include "vtkAssemblyNode.h" #include "vtkAssemblyPath.h" #include "vtkCamera.h" #include "vtkCommand.h" #include "vtkImageData.h" #include "vtkLODProp3D.h" #include "vtkMapper.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPoints.h" #include "vtkProp3DCollection.h" #include "vtkProperty.h" #include "vtkRenderWindow.h" #include "vtkRenderer.h" #include "vtkTransform.h" #include "vtkVertex.h" #include "vtkVolume.h" #include "vtkAbstractVolumeMapper.h" #include "vtkBox.h" #include "vtkImageActor.h" vtkCxxRevisionMacro(vtkPicker, "$Revision: 1.93 $"); vtkStandardNewMacro(vtkPicker); // Construct object with initial tolerance of 1/40th of window. There are no // pick methods and picking is performed from the renderer's actors. vtkPicker::vtkPicker() { this->Tolerance = 0.025; // 1/40th of the renderer window this->MapperPosition[0] = 0.0; this->MapperPosition[1] = 0.0; this->MapperPosition[2] = 0.0; this->Mapper = NULL; this->DataSet = NULL; this->GlobalTMin = VTK_DOUBLE_MAX; this->Actors = vtkActorCollection::New(); this->Prop3Ds = vtkProp3DCollection::New(); this->PickedPositions = vtkPoints::New(); this->Transform = vtkTransform::New(); } vtkPicker::~vtkPicker() { this->Actors->Delete(); this->Prop3Ds->Delete(); this->PickedPositions->Delete(); this->Transform->Delete(); } // Update state when prop3D is picked. void vtkPicker::MarkPicked(vtkAssemblyPath *path, vtkProp3D *prop3D, vtkAbstractMapper3D *m, double tMin, double mapperPos[3]) { int i; vtkMapper *mapper; vtkAbstractVolumeMapper *volumeMapper; this->SetPath(path); this->GlobalTMin = tMin; for (i=0; i < 3; i++) { this->MapperPosition[i] = mapperPos[i]; } if ( (mapper=vtkMapper::SafeDownCast(m)) != NULL ) { this->DataSet = mapper->GetInput(); this->Mapper = mapper; } else if ( (volumeMapper=vtkAbstractVolumeMapper::SafeDownCast(m)) != NULL ) { this->DataSet = volumeMapper->GetDataSetInput(); this->Mapper = volumeMapper; } else { this->DataSet = NULL; } // The point has to be transformed back into world coordinates. // Note: it is assumed that the transform is in the correct state. this->Transform->TransformPoint(mapperPos,this->PickPosition); // Invoke pick method if one defined - actor goes first prop3D->Pick(); this->InvokeEvent(vtkCommand::PickEvent,NULL); } // Perform pick operation with selection point provided. Normally the // first two values for the selection point are x-y pixel coordinate, and // the third value is =0. Return non-zero if something was successfully picked. int vtkPicker::Pick(double selectionX, double selectionY, double selectionZ, vtkRenderer *renderer) { int i; vtkProp *prop; vtkCamera *camera; vtkAbstractMapper3D *mapper = NULL; double p1World[4], p2World[4], p1Mapper[4], p2Mapper[4]; int picked=0; int *winSize; double x, y, t; double *viewport; double cameraPos[4], cameraFP[4]; double *displayCoords, *worldCoords; double *clipRange; double ray[3], rayLength; int pickable; int LODId; double windowLowerLeft[4], windowUpperRight[4]; double bounds[6], tol; double tF, tB; double hitPosition[3]; double cameraDOP[3]; // Initialize picking process this->Initialize(); this->Renderer = renderer; this->SelectionPoint[0] = selectionX; this->SelectionPoint[1] = selectionY; this->SelectionPoint[2] = selectionZ; // Invoke start pick method if defined this->InvokeEvent(vtkCommand::StartPickEvent,NULL); if ( renderer == NULL ) { vtkErrorMacro(<<"Must specify renderer!"); return 0; } // Get camera focal point and position. Convert to display (screen) // coordinates. We need a depth value for z-buffer. // camera = renderer->GetActiveCamera(); camera->GetPosition((double *)cameraPos); cameraPos[3] = 1.0; camera->GetFocalPoint((double *)cameraFP); cameraFP[3] = 1.0; renderer->SetWorldPoint(cameraFP[0],cameraFP[1],cameraFP[2],cameraFP[3]); renderer->WorldToDisplay(); displayCoords = renderer->GetDisplayPoint(); selectionZ = displayCoords[2]; // Convert the selection point into world coordinates. // renderer->SetDisplayPoint(selectionX, selectionY, selectionZ); renderer->DisplayToWorld(); worldCoords = renderer->GetWorldPoint(); if ( worldCoords[3] == 0.0 ) { vtkErrorMacro(<<"Bad homogeneous coordinates"); return 0; } for (i=0; i < 3; i++) { this->PickPosition[i] = worldCoords[i] / worldCoords[3]; } // Compute the ray endpoints. The ray is along the line running from // the camera position to the selection point, starting where this line // intersects the front clipping plane, and terminating where this // line intersects the back clipping plane. for (i=0; i<3; i++) { ray[i] = this->PickPosition[i] - cameraPos[i]; } for (i=0; i<3; i++) { cameraDOP[i] = cameraFP[i] - cameraPos[i]; } vtkMath::Normalize(cameraDOP); if (( rayLength = vtkMath::Dot(cameraDOP,ray)) == 0.0 ) { vtkWarningMacro("Cannot process points"); return 0; } clipRange = camera->GetClippingRange(); if ( camera->GetParallelProjection() ) { tF = clipRange[0] - rayLength; tB = clipRange[1] - rayLength; for (i=0; i<3; i++) { p1World[i] = this->PickPosition[i] + tF*cameraDOP[i]; p2World[i] = this->PickPosition[i] + tB*cameraDOP[i]; } } else { tF = clipRange[0] / rayLength; tB = clipRange[1] / rayLength; for (i=0; i<3; i++) { p1World[i] = cameraPos[i] + tF*ray[i]; p2World[i] = cameraPos[i] + tB*ray[i]; } } p1World[3] = p2World[3] = 1.0; // Compute the tolerance in world coordinates. Do this by // determining the world coordinates of the diagonal points of the // window, computing the width of the window in world coordinates, and // multiplying by the tolerance. // viewport = renderer->GetViewport(); winSize = renderer->GetRenderWindow()->GetSize(); x = winSize[0] * viewport[0]; y = winSize[1] * viewport[1]; renderer->SetDisplayPoint(x, y, selectionZ); renderer->DisplayToWorld(); renderer->GetWorldPoint(windowLowerLeft); x = winSize[0] * viewport[2]; y = winSize[1] * viewport[3]; renderer->SetDisplayPoint(x, y, selectionZ); renderer->DisplayToWorld(); renderer->GetWorldPoint(windowUpperRight); for (tol=0.0,i=0; i<3; i++) { tol += (windowUpperRight[i] - windowLowerLeft[i]) * (windowUpperRight[i] - windowLowerLeft[i]); } tol = sqrt (tol) * this->Tolerance; // Loop over all props. Transform ray (defined from position of // camera to selection point) into coordinates of mapper (not // transformed to actors coordinates! Reduces overall computation!!!). // Note that only vtkProp3D's can be picked by vtkPicker. // vtkPropCollection *props; vtkProp *propCandidate; if ( this->PickFromList ) { props = this->GetPickList(); } else { props = renderer->GetViewProps(); } vtkActor *actor; vtkLODProp3D *prop3D; vtkVolume *volume; vtkImageActor *imageActor = 0; vtkAssemblyPath *path; vtkProperty *tempProperty; this->Transform->PostMultiply(); vtkCollectionSimpleIterator pit; double scale[3]; for ( props->InitTraversal(pit); (prop=props->GetNextProp(pit)); ) { for ( prop->InitPathTraversal(); (path=prop->GetNextPath()); ) { pickable = 0; actor = NULL; propCandidate = path->GetLastNode()->GetViewProp(); if ( propCandidate->GetPickable() && propCandidate->GetVisibility() ) { pickable = 1; if ( (actor=vtkActor::SafeDownCast(propCandidate)) != NULL ) { mapper = actor->GetMapper(); if ( actor->GetProperty()->GetOpacity() <= 0.0 ) { pickable = 0; } } else if ( (prop3D=vtkLODProp3D::SafeDownCast(propCandidate)) != NULL ) { 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) != NULL) { prop3D->GetLODProperty(LODId, &tempProperty); if ( tempProperty->GetOpacity() <= 0.0 ) { pickable = 0; } } } else if ( (volume=vtkVolume::SafeDownCast(propCandidate)) != NULL ) { mapper = volume->GetMapper(); } else if ( (imageActor=vtkImageActor::SafeDownCast(propCandidate)) ) { mapper = 0; } else { pickable = 0; //only vtkProp3D's (actors and volumes) can be picked } } // If actor can be picked, get its composite matrix, invert it, and // use the inverted matrix to transform the ray points into mapper // coordinates. if ( pickable && mapper != NULL ) { vtkMatrix4x4 *LastMatrix = path->GetLastNode()->GetMatrix(); if (LastMatrix == NULL) { vtkErrorMacro (<< "Pick: Null matrix."); return 0; } this->Transform->SetMatrix(LastMatrix); this->Transform->Push(); this->Transform->Inverse(); this->Transform->GetScale(scale); //need to scale the tolerance this->Transform->TransformPoint(p1World,p1Mapper); this->Transform->TransformPoint(p2World,p2Mapper); for (i=0; i<3; i++) { ray[i] = p2Mapper[i] - p1Mapper[i]; } this->Transform->Pop(); // Have the ray endpoints in mapper space, now need to compare this // with the mapper bounds to see whether intersection is possible. // // Get the bounding box of the modeller. Note that the tolerance is // added to the bounding box to make sure things on the edge of the // bounding box are picked correctly. mapper->GetBounds(bounds); bounds[0] -= tol; bounds[1] += tol; bounds[2] -= tol; bounds[3] += tol; bounds[4] -= tol; bounds[5] += tol; if ( vtkBox::IntersectBox(bounds, (double *)p1Mapper, ray, hitPosition, t) ) { t = this->IntersectWithLine((double *)p1Mapper, (double *)p2Mapper, tol*0.333*(scale[0]+scale[1]+scale[2]), path, (vtkProp3D *)propCandidate, mapper); if ( t < VTK_DOUBLE_MAX ) { picked = 1; if ( ! this->Prop3Ds->IsItemPresent(prop) ) { this->Prop3Ds->AddItem((vtkProp3D *)prop); } this->PickedPositions->InsertNextPoint ((1.0 - t)*p1World[0] + t*p2World[0], (1.0 - t)*p1World[1] + t*p2World[1], (1.0 - t)*p1World[2] + t*p2World[2]); // backwards compatibility: also add to this->Actors if (actor) { this->Actors->AddItem(actor); } } } } else if ( pickable && imageActor ) { // special case for imageActor, which has no mapper vtkMatrix4x4 *LastMatrix = path->GetLastNode()->GetMatrix(); if (LastMatrix == NULL) { vtkErrorMacro (<< "Pick: Null matrix."); return 0; } this->Transform->SetMatrix(LastMatrix); this->Transform->Push(); this->Transform->Inverse(); this->Transform->TransformPoint(p1World,p1Mapper); this->Transform->TransformPoint(p2World,p2Mapper); this->Transform->Pop(); // Have the ray endpoints in data space, now need to compare this // with the displayed image bounds. imageActor->GetDisplayBounds(bounds); t = VTK_DOUBLE_MAX; for (i = 0; i < 3; i++) { if (bounds[2*i] == bounds[2*i+1] && p2Mapper[i] != p1Mapper[i]) { t = (p2World[i] - bounds[2*i])/(p2Mapper[i] - p1Mapper[i]); break; } } if (t < VTK_DOUBLE_MAX) { hitPosition[0] = (1.0 - t)*p1Mapper[0] + t*p2Mapper[0]; hitPosition[1] = (1.0 - t)*p1Mapper[1] + t*p2Mapper[1]; hitPosition[2] = (1.0 - t)*p1Mapper[2] + t*p2Mapper[2]; if ((bounds[0] == bounds[1] || (hitPosition[0] >= bounds[0]-tol && hitPosition[0] <= bounds[1]+tol)) && (bounds[2] == bounds[3] || (hitPosition[1] >= bounds[2]-tol && hitPosition[1] <= bounds[3]+tol)) && (bounds[4] == bounds[5] || (hitPosition[2] >= bounds[4]-tol && hitPosition[2] <= bounds[5]+tol))) { picked = 1; // the following code is handled in MarkPicked for other Prop3Ds this->Mapper = mapper; // mapper is null this->DataSet = imageActor->GetInput(); this->MapperPosition[0] = hitPosition[0]; this->MapperPosition[1] = hitPosition[1]; this->MapperPosition[2] = hitPosition[2]; this->Transform->TransformPoint(hitPosition,this->PickPosition); imageActor->Pick(); this->InvokeEvent(vtkCommand::PickEvent,NULL); this->Prop3Ds->AddItem(imageActor); this->PickedPositions->InsertNextPoint ((1.0 - t)*p1World[0] + t*p2World[0], (1.0 - t)*p1World[1] + t*p2World[1], (1.0 - t)*p1World[2] + t*p2World[2]); } } }//if visible and pickable not transparent and has mapper }//for all parts }//for all actors // Invoke end pick method if defined this->InvokeEvent(vtkCommand::EndPickEvent,NULL); return picked; } // Intersect data with specified ray. double vtkPicker::IntersectWithLine(double p1[3], double p2[3], double vtkNotUsed(tol), vtkAssemblyPath *path, vtkProp3D *prop3D, vtkAbstractMapper3D *mapper) { int i; double center[3], t, ray[3], rayFactor; // Get the data from the modeler mapper->GetCenter(center); for (i=0; i<3; i++) { ray[i] = p2[i] - p1[i]; } if (( rayFactor = vtkMath::Dot(ray,ray)) == 0.0 ) { return 2.0; } // Project the center point onto the ray and determine its parametric value // t = (ray[0]*(center[0]-p1[0]) + ray[1]*(center[1]-p1[1]) + ray[2]*(center[2]-p1[2])) / rayFactor; if ( t >= 0.0 && t <= 1.0 && t < this->GlobalTMin ) { this->MarkPicked(path, prop3D, mapper, t, center); } return t; } // Initialize the picking process. void vtkPicker::Initialize() { this->vtkAbstractPropPicker::Initialize(); this->Actors->RemoveAllItems(); this->Prop3Ds->RemoveAllItems(); this->PickedPositions->Reset(); this->MapperPosition[0] = 0.0; this->MapperPosition[1] = 0.0; this->MapperPosition[2] = 0.0; this->Mapper = NULL; this->GlobalTMin = VTK_DOUBLE_MAX; } vtkActorCollection *vtkPicker::GetActors() { if (this->Actors->GetNumberOfItems() != this->PickedPositions->GetNumberOfPoints()) { vtkWarningMacro(<<"Not all Prop3Ds are actors, use GetProp3Ds instead"); } return this->Actors; } void vtkPicker::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); if ( this->DataSet ) { os << indent << "DataSet: " << this->DataSet << "\n"; } else { os << indent << "DataSet: (none)"; } os << indent << "Mapper: " << this->Mapper << "\n"; os << indent << "Tolerance: " << this->Tolerance << "\n"; os << indent << "Mapper Position: (" << this->MapperPosition[0] << "," << this->MapperPosition[1] << "," << this->MapperPosition[2] << ")\n"; }