void CameraDolly::moveToPoly(vtkPolyData & polyData, vtkIdType index, IndexType indexType, bool overTime) { if (!m_renderer) { return; } double selectionPoint[3], selectionNormal[3]; if (indexType == IndexType::cells) { vtkCell * cell = polyData.GetCell(index); assert(cell); if (!cell) { qWarning() << "[CameraDolly] Cell not found in data set: " + QString::number(index); return; } auto cellPointIds = vtkSmartPointer<vtkIdTypeArray>::New(); cellPointIds->SetArray(cell->GetPointIds()->GetPointer(0), cell->GetNumberOfPoints(), true); vtkPolygon::ComputeCentroid(cellPointIds, polyData.GetPoints(), selectionPoint); vtkPolygon::ComputeNormal(cell->GetPoints(), selectionNormal); } else { polyData.GetPoint(index, selectionPoint); auto normals = polyData.GetPointData()->GetNormals(); if (!normals) { qWarning() << "[CameraDolly] No point normals found in poly data set"; return; } normals->GetTuple(index, selectionNormal); } vtkCamera & camera = *m_renderer->GetActiveCamera(); double objectCenter[3]; polyData.GetCenter(objectCenter); double startingFocalPoint[3], targetFocalPoint[3]; double startingAzimuth, targetAzimuth; double startingElevation, targetElevation; camera.GetFocalPoint(startingFocalPoint); for (size_t i = 0; i < 3; ++i) // look at center of the object { targetFocalPoint[i] = objectCenter[i]; } startingAzimuth = TerrainCamera::getAzimuth(camera); startingElevation = TerrainCamera::getVerticalElevation(camera); // compute target camera position to find azimuth and elevation for the transition double startingPosition[3]; camera.GetPosition(startingPosition); double targetPositionXY[2]; double objectToEye[3]; vtkMath::Subtract(startingPosition, objectCenter, objectToEye); double viewDistanceXY = vtkMath::Normalize2D(objectToEye); double selectionCenterXY[2] = { selectionPoint[0], selectionPoint[1] }; double norm_objectToSelectionXY[2]; norm_objectToSelectionXY[0] = selectionCenterXY[0] - objectCenter[0]; norm_objectToSelectionXY[1] = selectionCenterXY[1] - objectCenter[1]; double selectionRadiusXY = vtkMath::Normalize2D(norm_objectToSelectionXY); // make sure to move outside of the selection if (viewDistanceXY < selectionRadiusXY) { viewDistanceXY = selectionRadiusXY * 1.5; } // choose nearest viewpoint for flat surfaces const double flat_threshold = 30; double inclination = std::acos(selectionNormal[2]) * 180.0 / vtkMath::Pi(); if (inclination < flat_threshold) { targetPositionXY[0] = objectCenter[0] + viewDistanceXY * norm_objectToSelectionXY[0]; targetPositionXY[1] = objectCenter[1] + viewDistanceXY * norm_objectToSelectionXY[1]; } // or use the hill's normal else { double selectionNormalXY[2] = { selectionNormal[0], selectionNormal[1] }; double l; if ((l = std::sqrt((selectionNormalXY[0] * selectionNormalXY[0] + selectionNormalXY[1] * selectionNormalXY[1]))) != 0.0) { selectionNormalXY[0] /= l; selectionNormalXY[1] /= l; } // get a point in front of the selected cell double selectionFrontXY[2] = { selectionCenterXY[0] + selectionNormalXY[0], selectionCenterXY[1] + selectionNormalXY[1] }; double intersections[4]; // our focal point (center of view circle) is the object center // so assume a circle center of (0,0) in the calculations bool intersects = circleLineIntersection(viewDistanceXY, selectionCenterXY, selectionFrontXY, &intersections[0], &intersections[2]); // ignore for now if (!intersects) { targetPositionXY[0] = startingPosition[0]; targetPositionXY[1] = startingPosition[1]; } else { bool towardsPositive = selectionFrontXY[0] > selectionCenterXY[0] || (selectionFrontXY[0] == selectionCenterXY[0] && selectionFrontXY[1] >= selectionCenterXY[1]); int intersectionIndex; if (towardsPositive == (intersections[0] > intersections[2] || (intersections[0] == intersections[2] && intersections[1] >= intersections[3]))) { intersectionIndex = 0; } else { intersectionIndex = 2; } targetPositionXY[0] = intersections[intersectionIndex]; targetPositionXY[1] = intersections[intersectionIndex + 1]; } } auto targetCamera = vtkSmartPointer<vtkCamera>::New(); targetCamera->SetPosition(targetPositionXY[0], targetPositionXY[1], startingPosition[2]); targetCamera->SetFocalPoint(targetFocalPoint); targetCamera->SetViewUp(camera.GetViewUp()); targetAzimuth = TerrainCamera::getAzimuth(*targetCamera); targetElevation = TerrainCamera::getVerticalElevation(*targetCamera); if (overTime) { const double flyTimeSec = 0.5; const int flyDeadlineMSec = 1000; const int NumberOfFlyFrames = 15; double stepFactor = 1.0 / (NumberOfFlyFrames + 1); // transition of position, focal point, azimuth and elevation double stepFocalPoint[3], stepPosition[3], stepAzimuth, stepElevation; vtkMath::Subtract(targetFocalPoint, startingFocalPoint, stepFocalPoint); vtkMath::MultiplyScalar(stepFocalPoint, stepFactor); vtkMath::Subtract(targetCamera->GetPosition(), startingPosition, stepPosition); vtkMath::MultiplyScalar(stepPosition, stepFactor); stepAzimuth = (targetAzimuth - startingAzimuth) * stepFactor; stepElevation = (targetElevation - startingElevation) * stepFactor; double intermediateFocal[3]{ startingFocalPoint[0], startingFocalPoint[1], startingFocalPoint[2] }; double intermediatePosition[3]{ startingPosition[0], startingPosition[1], startingPosition[2] }; double intermediateAzimuth = startingAzimuth; double intermediateElevation = startingElevation; QTime startingTime = QTime::currentTime(); QTime deadline = startingTime.addMSecs(flyDeadlineMSec); long sleepMSec = long(flyTimeSec * 1000.0 * stepFactor); QTime renderTime; int i = 0; for (; i < NumberOfFlyFrames; ++i) { renderTime.start(); vtkMath::Add(intermediateFocal, stepFocalPoint, intermediateFocal); vtkMath::Add(intermediatePosition, stepPosition, intermediatePosition); intermediateAzimuth += stepAzimuth; intermediateElevation += stepElevation; camera.SetFocalPoint(intermediateFocal); camera.SetPosition(intermediatePosition); TerrainCamera::setAzimuth(camera, intermediateAzimuth); TerrainCamera::setVerticalElevation(camera, intermediateElevation); m_renderer->ResetCameraClippingRange(); m_renderer->GetRenderWindow()->InvokeEvent(vtkCommandExt::ForceRepaintEvent); if (QTime::currentTime() > deadline) { break; } QThread::msleep((unsigned long)std::max(0l, sleepMSec - renderTime.elapsed())); } } // in any case, jump to target position camera.SetFocalPoint(targetFocalPoint); camera.SetPosition(targetCamera->GetPosition()); TerrainCamera::setAzimuth(camera, targetAzimuth); TerrainCamera::setVerticalElevation(camera, targetElevation); m_renderer->ResetCameraClippingRange(); m_renderer->GetRenderWindow()->Render(); }