Builds a translucent + wireframe sphere centered at `center` with the given `radius`. Used to visualize omnidirectional (e.g. EQUIRECTANGULAR) cameras, which observe the full sphere and have no pinhole frustum. The triangle mesh provides the translucent fill and also makes the camera selectable (picking is done against triangles); the lines form the wireframe. The sphere is oriented by `world_from
| 94 | // along the camera's forward axis (azimuth 0, elevation 0) marks the image |
| 95 | // center. |
| 96 | void BuildSphericalCameraModel( |
| 97 | const Eigen::Vector3f& center, |
| 98 | const float radius, |
| 99 | const Eigen::Matrix3f& world_from_cam_rot, |
| 100 | const RGBAColor& plane_color, |
| 101 | const RGBAColor& frame_color, |
| 102 | std::vector<TrianglePainter::Data>* triangle_data, |
| 103 | std::vector<LinePainter::Data>* line_data, |
| 104 | std::vector<LinePainter::Data>* axis_line_data) { |
| 105 | constexpr int kNumStacks = 6; // Latitude (elevation) divisions. |
| 106 | constexpr int kNumSlices = 12; // Longitude (azimuth) divisions. |
| 107 | |
| 108 | // Sphere point at the given elevation/azimuth grid index, using the same |
| 109 | // equirectangular parametrization as the EQUIRECTANGULAR camera model so that |
| 110 | // the wireframe aligns with the image: stack 0..N maps elevation +pi/2..-pi/2 |
| 111 | // (image top to bottom), slice 0..N maps azimuth -pi..pi (image left to |
| 112 | // right). The camera-frame bearing is rotated into world space. |
| 113 | const auto sphere_point = [&](const int stack, |
| 114 | const int slice) -> Eigen::Vector3f { |
| 115 | const double phi = |
| 116 | EIGEN_PI * (0.5 - static_cast<double>(stack) / kNumStacks); |
| 117 | const double theta = |
| 118 | 2.0 * EIGEN_PI * (static_cast<double>(slice) / kNumSlices - 0.5); |
| 119 | const double cos_phi = std::cos(phi); |
| 120 | const Eigen::Vector3f ray_in_cam( |
| 121 | static_cast<float>(cos_phi * std::sin(theta)), |
| 122 | static_cast<float>(-std::sin(phi)), |
| 123 | static_cast<float>(cos_phi * std::cos(theta))); |
| 124 | return center + radius * (world_from_cam_rot * ray_in_cam); |
| 125 | }; |
| 126 | |
| 127 | if (triangle_data != nullptr) { |
| 128 | const auto add_triangle = [&](const Eigen::Vector3f& p1, |
| 129 | const Eigen::Vector3f& p2, |
| 130 | const Eigen::Vector3f& p3) { |
| 131 | triangle_data->emplace_back(PointPainter::Data(p1(0), |
| 132 | p1(1), |
| 133 | p1(2), |
| 134 | plane_color(0), |
| 135 | plane_color(1), |
| 136 | plane_color(2), |
| 137 | plane_color(3)), |
| 138 | PointPainter::Data(p2(0), |
| 139 | p2(1), |
| 140 | p2(2), |
| 141 | plane_color(0), |
| 142 | plane_color(1), |
| 143 | plane_color(2), |
| 144 | plane_color(3)), |
| 145 | PointPainter::Data(p3(0), |
| 146 | p3(1), |
| 147 | p3(2), |
| 148 | plane_color(0), |
| 149 | plane_color(1), |
| 150 | plane_color(2), |
| 151 | plane_color(3))); |
| 152 | }; |
| 153 | for (int i = 0; i < kNumStacks; ++i) { |