| 324 | } |
| 325 | |
| 326 | void BuildCameraModel(const std::optional<Rigid3d>& cam_from_world, |
| 327 | const Camera& camera, |
| 328 | const float image_size, |
| 329 | const RGBAColor& plane_color, |
| 330 | const RGBAColor& frame_color, |
| 331 | const Eigen::Vector3d& model_origin, |
| 332 | const double model_scale, |
| 333 | const bool show_camera_orientation, |
| 334 | std::vector<TrianglePainter::Data>* triangle_data, |
| 335 | std::vector<LinePainter::Data>* line_data, |
| 336 | std::vector<LinePainter::Data>* axis_line_data) { |
| 337 | // Updating the reconstruction in the viewer (e.g., deleting an image or a |
| 338 | // point) is not thread-safe when the mapper is running, where some images may |
| 339 | // be in a partial, incorrect state. In rare circumstances, an image may be |
| 340 | // registered but not yet have a pose. Instead of crashing the viewer, we |
| 341 | // simply skip the visualization of these images and warn the user. |
| 342 | if (!cam_from_world) { |
| 343 | LOG(WARNING) << "Failed to render camera because it has no pose."; |
| 344 | return; |
| 345 | } |
| 346 | |
| 347 | // Generate camera dimensions in OpenGL (world) coordinate space. |
| 348 | const float kBaseCameraWidth = 1024.0f; |
| 349 | const float image_width = image_size * camera.width / kBaseCameraWidth; |
| 350 | const float image_height = image_width * static_cast<float>(camera.height) / |
| 351 | static_cast<float>(camera.width); |
| 352 | const float image_extent = std::max(image_width, image_height); |
| 353 | |
| 354 | Rigid3d world_from_cam = Inverse(*cam_from_world); |
| 355 | world_from_cam.translation() += model_origin; |
| 356 | world_from_cam.translation() *= model_scale; |
| 357 | |
| 358 | const Eigen::Matrix<float, 3, 4> world_from_cam_mat = |
| 359 | world_from_cam.ToMatrix().cast<float>(); |
| 360 | |
| 361 | const Eigen::Vector3f pc = world_from_cam_mat.rightCols<1>(); |
| 362 | |
| 363 | // Spherical (equirectangular) cameras observe the full sphere and have no |
| 364 | // pinhole image plane, so a frustum is meaningless. Visualize them as a |
| 365 | // sphere centered at the projection center instead; all other models use a |
| 366 | // perspective viewing frustum. |
| 367 | if (camera.IsSpherical()) { |
| 368 | const double radius = image_extent / 2.0; |
| 369 | BuildSphericalCameraModel(pc, |
| 370 | radius, |
| 371 | world_from_cam_mat.block<3, 3>(0, 0), |
| 372 | plane_color, |
| 373 | frame_color, |
| 374 | triangle_data, |
| 375 | line_data, |
| 376 | axis_line_data); |
| 377 | } else { |
| 378 | BuildPerspectiveCameraModel(world_from_cam_mat, |
| 379 | camera, |
| 380 | image_width, |
| 381 | image_height, |
| 382 | image_extent, |
| 383 | plane_color, |
no test coverage detected