ptcloud: cv::viz3d OpenGL point-cloud/mesh visualization

This commit is contained in:
kirtijindal14
2026-07-27 13:37:56 +05:30
parent 53d308c2a5
commit 3bc72d4efc
11 changed files with 21083 additions and 5 deletions
+2 -5
View File
@@ -1,10 +1,7 @@
set(the_description "High level point cloud and mesh operations")
set(debug_modules "")
if(DEBUG_opencv_ptcloud)
list(APPEND debug_modules opencv_highgui)
endif()
ocv_define_module(ptcloud opencv_geometry opencv_imgproc opencv_features opencv_video ${debug_modules}
# viz3d visualization lives here and imports the window + OpenGL context from highgui.
ocv_define_module(ptcloud opencv_geometry opencv_imgproc opencv_features opencv_video opencv_flann opencv_highgui
WRAP java objc python js
)
ocv_target_link_libraries(${the_module} ${LAPACK_LIBRARIES})
@@ -12,6 +12,7 @@
#include "opencv2/ptcloud/odometry_frame.hpp"
#include "opencv2/ptcloud/odometry_settings.hpp"
#include "opencv2/ptcloud/slam.hpp"
#include "opencv2/ptcloud/viz3d.hpp"
/**
@defgroup ptcloud Point Cloud Processing
@@ -0,0 +1,188 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef OPENCV_PTCLOUD_VIZ3D_HPP
#define OPENCV_PTCLOUD_VIZ3D_HPP
#include "opencv2/core.hpp"
namespace cv { namespace viz3d {
//! Render modes for cv::viz3d::showBox / cv::viz3d::showPlane / cv::viz3d::showSphere
enum RenderMode
{
RENDER_SIMPLE = 0,
RENDER_SHADING = 1,
RENDER_WIREFRAME = 2,
};
/** @brief Sets the view's perspective on a viz3d window.
@param win_name Name of the viz3d window.
@param fov Vertical field of view in radians.
@param z_near Minimum clip distance.
@param z_far Maximum clip distance.
*/
CV_EXPORTS_W void setPerspective(const String& win_name, float fov, float z_near, float z_far);
/** @brief Hides or shows the grid on a viz3d window.
@param win_name Name of the viz3d window.
@param visible Should the grid be shown?
*/
CV_EXPORTS_W void setGridVisible(const String& win_name, bool visible);
/** @brief Sets the properties of the light used on a viz3d window.
@param win_name Name of the viz3d window.
@param direction Direction any point to the sun.
@param ambient Ambient light color.
@param diffuse Diffuse light color.
*/
CV_EXPORTS_W void setSun(const String& win_name, const Vec3f& direction, const Vec3f& ambient, const Vec3f& diffuse);
/** @brief Sets the properties of the sky used on a viz3d window.
@param win_name Name of the viz3d window.
@param color Sky color.
*/
CV_EXPORTS_W void setSky(const String& win_name, const Vec3f& color);
/** @brief Shows a box object in the specified viz3d window. See cv::viz3d::destroyObject.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param size Box size.
@param color Box color.
@param mode Render mode.
*/
CV_EXPORTS_W void showBox(const String& win_name, const String& obj_name, const Vec3f& size, const Vec3f& color, RenderMode mode = RENDER_SIMPLE);
/** @brief Shows a plane object in the specified viz3d window. See cv::viz3d::destroyObject.
The place faces the positive Y axis by default.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param size Plane size.
@param color Plane color.
@param mode Render mode.
*/
CV_EXPORTS_W void showPlane(const String& win_name, const String& obj_name, const Vec2f& size, const Vec3f& color, RenderMode mode = RENDER_SIMPLE);
/** @brief Shows a sphere object in the specified viz3d window. See cv::viz3d::destroyObject.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param radius Sphere radius.
@param color Sphere color.
@param mode Render mode.
@param divs The higher this integer is the more detail (triangles) the sphere has.
*/
CV_EXPORTS_W void showSphere(const String& win_name, const String& obj_name, float radius, const Vec3f& color, RenderMode mode = RENDER_SIMPLE, int divs = 3);
/** @brief Shows a camera trajectory object in the specified viz3d window. See cv::viz3d::destroyObject.
The camera trajectory data array must be 2D. Each row has a width of 6, where the first
3 components are the position and the second 3 components are the direction of the camera.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param trajectory Camera trajectory data.
@param aspect Aspect ratio of the camera frustum.
@param scale Scale applied to camera frustums.
@param frustum_color Color of the frustums.
@param line_color Color of the line.
*/
// Not CV_EXPORTS_W: the auto-bindings generator mishandles the two trailing
// Vec3f defaults (like the other Vec3f-taking viz3d functions, it stays C++-only).
CV_EXPORTS void showCameraTrajectory(
const String& win_name, const String& obj_name, InputArray trajectory,
float aspect, float scale, Vec3f frustum_color = Vec3f(1.0f, 1.0f, 1.0f),
Vec3f line_color = Vec3f(0.5f, 0.5f, 0.5f));
/** @brief Shows a mesh object in the specified viz3d window. See cv::viz3d::destroyObject.
The vertices array must be 2D. If shading is disabled (the default option), each row has a width of
3 (xyz), 6 (xyz, rgb) or 9 (xyz, rgb, uvw) where uvw is the normal, so that each row represents a vertex.
Shading is only enabled when using normals.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param verts Vertices input array.
@param indices Indices input array.
*/
CV_EXPORTS void showMesh(const String& win_name, const String& obj_name, InputArray verts, InputArray indices);
/** @overload Shows a mesh object in the specified viz3d window. See cv::viz3d::destroyObject.
The vertices array must be 2D, where each row has a width of 3 (xyz), 6 (xyz, rgb) or 9 (xyz, rgb, uvw)
where uvw is the normal, so that each row represents a vertex. Shading is only enabled when using normals.
Points are grouped in triplets to make triangles (points 1, 2 and 3, points 4, 5 and 6, etc).
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param verts Vertices input array.
@param indices Indices input array.
*/
CV_EXPORTS_AS(showMesh2) void showMesh(const String& win_name, const String& obj_name, InputArray verts);
/** @brief Shows a point cloud object in the specified viz3d window. See cv::viz3d::destroyObject.
The points array must be 2D, where each row has either a width of 6 (xyz, rgb), so that
each row represents a point.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param points Vertices input array.
*/
CV_EXPORTS_W void showPoints(const String& win_name, const String& obj_name, InputArray points);
/** @brief Shows a colored depth map as a point cloud in the specified viz3d window. See cv::viz3d::destroyObject.
The colored depth map must be a 2D image with 4 channels (RGBD). The points are created assuming
that positive depth means positive position in the Z axis.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param img Input colored depth map.
@param intrinsics Camera intrinsics matrix.
@param scale Scale applied to the coordinates of the points.
*/
CV_EXPORTS_W void showRGBD(const String& win_name, const String& obj_name, InputArray img, const Matx33f& intrinsics, float scale = 1.0f);
/** @brief Shows a lines object in the specified viz3d window. See cv::viz3d::destroyObject.
The points array must be 2D, where each row has either a width of 6 (xyz, rgb), so that
each row represents a point. Points are grouped in pairs to make lines (points 1 and 2,
points 3 and 4, etc).
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param points Vertices input array.
*/
CV_EXPORTS_W void showLines(const String& win_name, const String& obj_name, InputArray points);
/** @brief Sets an object's position in the specified viz3d window.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param position New position.
*/
CV_EXPORTS_W void setObjectPosition(const String& win_name, const String& obj_name, const Vec3f& position);
/** @brief Sets an object's rotation in the specified viz3d window.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
@param rotation New rotation in euler angles (radians).
*/
CV_EXPORTS_W void setObjectRotation(const String& win_name, const String& obj_name, const Vec3f& rotation);
/** @brief Destroys an object created with cv::viz3d::showMesh or cv::viz3d::showPoints.
@param win_name Name of the viz3d window.
@param obj_name Name of the object.
*/
CV_EXPORTS_W void destroyObject(const String& win_name, const String& obj_name);
} // namespace viz3d
} // namespace cv
#endif
+156
View File
@@ -0,0 +1,156 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include <fstream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/ptcloud.hpp> // cv::viz3d now lives in the ptcloud module
using namespace cv;
Mat loadPoints(const String& path)
{
Mat points;
std::ifstream ifs(path);
int _;
float x, y, z, r, g, b;
Vec3f c = Vec3f::all(0.0f);
int count;
std::string str;
std::getline(ifs, str);
for (count = 0; ifs >> _ && ifs >> x && ifs >> z && ifs >> y && ifs >> r && ifs >> g && ifs >> b; ++count)
{
y = -y;
float data[] = { x, y, z, r / 255.0f, g / 255.0f, b / 255.0f };
points.push_back(Mat(Size(6, 1), CV_32F, &data));
c += Vec3f(x, y, z);
}
c /= count;
for (int i = 0; i < count; ++i)
for (int j = 0; j < 3; ++j)
points.at<float>(i, j) -= c(j);
return points;
}
int main()
{
float verts_data[] = {
-0.5, -0.5, 0.0,
-0.5, +0.5, 0.0,
+0.5, +0.5, 0.0,
+0.5, -0.5, 0.0,
};
Mat verts_mat = Mat(Size(3, 4), CV_32F, &verts_data);
int indices_data[] = {
0, 1, 2,
2, 3, 0,
};
Mat indices_mat = Mat(Size(3, 2), CV_32S, &indices_data);
float point_data[] = {
-0.5, -0.5, 0.0, 1.0f, 0.0f, 0.0f,
-0.5, +0.5, 0.0, 0.0f, 1.0f, 0.0f,
+0.5, +0.5, 0.0, 0.0f, 0.0f, 1.0f,
+0.5, -0.5, 0.0, 1.0f, 1.0f, 1.0f,
};
Mat points_mat = Mat(Size(6, 4), CV_32F, &point_data);
float trajectory_data[] = {
-0.0, +5.0, +0.0, -1.0f, -1.0f, -1.0f,
-5.0, +4.0, +2.0, +0.0f, +0.0f, -1.0f,
-10.0, +3.0, +5.0, -0.5f, +0.2f, -0.5f,
};
Mat trajectory_mat = Mat(Size(6, 3), CV_32F, &trajectory_data);
// Images taken from https://rgbd-dataset.cs.washington.edu
// Papers that need to be cited to use this data:
// [1] N. Silberman, D. Hoiem, P. Kohli, R. Fergus. Indoor segmentation and support inference from rgbd images. In ECCV, 2012.
// [2] A.Janoch, S.Karayev, Y.Jia, J.T.Barron, M.Fritz, K.Saenko, and T.Darrell.A category - level 3 - d object dataset : Putting the kinect to work.In ICCV Workshop on Consumer Depth Cameras for Computer Vision, 2011.
// [3] J.Xiao, A.Owens, and A.Torralba.SUN3D : A database of big spaces reconstructed using SfMand object labels.In ICCV, 2013
Mat rgbd_components[] = {
imread(samples::findFile("rgbd-color.jpg"), IMREAD_COLOR),
imread(samples::findFile("rgbd-depth.png"), IMREAD_ANYDEPTH | IMREAD_ANYCOLOR)
};
rgbd_components[0].convertTo(rgbd_components[0], CV_32F);
rgbd_components[1].convertTo(rgbd_components[1], CV_32F);
Mat rgbd;
merge(rgbd_components, 2, rgbd);
cvtColor(rgbd, rgbd, COLOR_BGRA2RGBA);
// Point cloud data taken from https://sketchfab.com/3d-models/anthidium-forcipatum-point-cloud-3493da15a8db4f34929fc38d9d0fcb2c
Mat bee_mat = loadPoints(samples::findFile("anthidium-forcipatum.csv"));
// Show two instances of the same example mesh
viz3d::showMesh("viz3d", "mesh1", verts_mat, indices_mat);
viz3d::showMesh("viz3d", "mesh2", verts_mat, indices_mat);
viz3d::setObjectPosition("viz3d", "mesh1", { -2.0f, 0.0f, 0.0f });
viz3d::setObjectPosition("viz3d", "mesh2", { 2.0f, 0.0f, 0.0f });
// Show an example point cloud
viz3d::showPoints("viz3d", "points", points_mat);
// Show a bee point cloud
viz3d::showPoints("viz3d", "bee", bee_mat);
viz3d::setObjectPosition("viz3d", "bee", { 0.0f, 0.0f, 5.0f });
// Show RGBD image as points
viz3d::showRGBD("rgbd", "rgbd", rgbd, {
529.5f, 0.0f, 365.0f,
0.0f, 529.5f, 265.0f,
0.0f, 0.0f, 1.0f
}, 0.1f);
viz3d::setObjectPosition("rgbd", "rgbd", { 0.0f, 0.0f, 0.0f });
viz3d::setGridVisible("rgbd", true);
// Show a solid box
viz3d::showBox("viz3d", "box1", { 0.25f, 0.25f, 0.25f }, { 0.5f, 1.0f, 0.5f });
viz3d::setObjectPosition("viz3d", "box1", { -5.0f, 0.0f, -5.0f });
// Show 3 wireframe boxes
viz3d::showBox("viz3d", "box2", { 1.0f, 0.5f, 0.5f }, { 1.0f, 0.5f, 0.5f }, viz3d::RENDER_SIMPLE);
viz3d::showBox("viz3d", "box3", { 0.5f, 1.0f, 0.5f }, { 0.5f, 1.0f, 0.5f }, viz3d::RENDER_WIREFRAME);
viz3d::showBox("viz3d", "box4", { 0.5f, 0.5f, 1.0f }, { 0.5f, 0.5f, 1.0f}, viz3d::RENDER_SHADING);
viz3d::setObjectPosition("viz3d", "box2", { 5.0f, 0.0f, 5.0f });
viz3d::setObjectPosition("viz3d", "box3", { -5.0f, 0.0f, 5.0f });
viz3d::setObjectPosition("viz3d", "box4", { -5.0f, 0.0f, 0.0f });
// Show a solid sphere
viz3d::showSphere("viz3d", "sphere1", 1.0f, { 0.7f, 0.9f, 0.7f }, viz3d::RENDER_SHADING);
viz3d::setObjectPosition("viz3d", "sphere1", { 0.0f, 0.0f, -5.0f });
// Show wireframe sphere
viz3d::showSphere("viz3d", "sphere2", 1.0f, { 0.7f, 0.9f, 0.7f }, viz3d::RENDER_WIREFRAME);
viz3d::setObjectPosition("viz3d", "sphere2", { 5.0f, 0.0f, 0.0f });
// Show plane
viz3d::showPlane("viz3d", "plane1", { 1.5f, 1.0f }, { 0.8f, 0.5f, 0.3f });
viz3d::setObjectPosition("viz3d", "plane1", { 5.0f, 0.0f, -5.0f });
viz3d::showCameraTrajectory("viz3d", "trajectory", trajectory_mat, 1.5f, 0.2f);
float x = 0.0f;
while (waitKey(16) != 27)
{
// Animate objects
viz3d::setObjectRotation("viz3d", "mesh1", { x, 0.0f, 0.0f });
viz3d::setObjectRotation("viz3d", "mesh2", { 0.0f, 0.0f, x });
x += 0.01f;
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef OPENCV_PTCLOUD_VIZ3D_GRID_TICKS_HPP
#define OPENCV_PTCLOUD_VIZ3D_GRID_TICKS_HPP
namespace cv { namespace viz3d { namespace detail {
// Grid line spacing, snapped so dist_scale / tick_step stays in [2, 4].
// Pure math (no OpenGL) so it is unit-testable headless. Terminates for any
// finite dist_scale (regression guard for the old logf(1.0)=0 -> inf hang).
inline float gridTickStep(float dist_scale)
{
float tick_step = 1.0f;
while (dist_scale / tick_step > 4.0f)
tick_step *= 2.0f;
while (tick_step > 1e-6f && dist_scale / tick_step < 2.0f)
tick_step *= 0.5f;
return tick_step;
}
}}} // namespace cv::viz3d::detail
#endif
+1592
View File
@@ -0,0 +1,1592 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "../precomp.hpp"
#include "viz3d_private.hpp"
#include "grid_ticks.hpp"
#include "opencv2/core/utils/logger.hpp"
#include "opencv2/imgproc.hpp"
namespace cv { namespace viz3d {
#ifdef HAVE_OPENGL
static void openGlDrawCallback(void* data)
{
Window* win = static_cast<Window*>(data);
if (win)
win->draw();
}
static void openGlFreeCallback(void* data)
{
Window* win = static_cast<Window*>(data);
if (win)
delete win;
}
static void mouseCallback(int event, int x, int y, int flags, void* data)
{
Window* win = static_cast<Window*>(data);
if (win)
win->onMouse(event, x, y, flags);
}
static Window* getWindow(const String& win_name)
{
namedWindow(win_name, WINDOW_OPENGL);
const double useGl = getWindowProperty(win_name, WND_PROP_OPENGL);
if (useGl <= 0)
CV_Error(cv::Error::StsBadArg, "OpenCV/UI: viz3d can't be used because the window was created without an OpenGL context");
Window* win = static_cast<Window*>(getOpenGlUserData(win_name));
if (!win)
{
setOpenGlContext(win_name);
std::unique_ptr<Window> new_win(new Window(win_name));
setOpenGlDrawCallback(win_name, &openGlDrawCallback, new_win.get());
setOpenGlFreeCallback(win_name, &openGlFreeCallback);
setMouseCallback(win_name, &mouseCallback, new_win.get());
win = new_win.release();
}
else
{
auto callback = getOpenGlDrawCallback(win_name);
if (callback && callback != openGlDrawCallback)
CV_Error(cv::Error::StsBadArg, "OpenCV/UI: viz3d can't be used because the OpenGL callback is already being used on this window");
}
return win;
}
// Generates vertices for a box.
static void generateBox(Mat& mat, const Vec3f& size, const Vec3f& color, RenderMode mode)
{
if (mode == RENDER_SHADING)
mat.create(6 * 6, 9, CV_32F); // 6 faces, each with 6 vertices, and 9 floats per vertex (position + color + normal)
else if (mode == RENDER_SIMPLE)
mat.create(6 * 6, 6, CV_32F); // 6 faces, each with 6 vertices, and 6 floats per vertex (position + color)
else if (mode == RENDER_WIREFRAME)
mat.create(8 * 6, 6, CV_32F); // 6 faces, each with 8 vertices, and 6 floats per vertex (position + color)
int next_row = 0;
for (int s = -1; s <= 1; s += 2) // For each side
for (int a = 0; a < 3; ++a) // For each axis
{
// Get the normal vector.
Vec3f normal = Vec3f::all(0.0f);
normal((a + 2) % 3) = static_cast<float>(s);
// Get offset vectors
Vec3f offset_x, offset_y, offset_z;
offset_x = offset_y = offset_z = Vec3f::all(0.0f);
offset_x((a + 0) % 3) = size((a + 0) % 3);
offset_y((a + 1) % 3) = size((a + 1) % 3);
offset_z((a + 2) % 3) = static_cast<float>(s) * size((a + 2) % 3);
// Generate vertex positions
Vec3f positions[8];
int count = 0;
if (mode == RENDER_WIREFRAME) {
// Face line segments
count = 8;
positions[0] = offset_z - offset_x - offset_y;
positions[1] = offset_z + offset_x - offset_y;
positions[2] = offset_z + offset_x - offset_y;
positions[3] = offset_z + offset_x + offset_y;
positions[4] = offset_z + offset_x + offset_y;
positions[5] = offset_z - offset_x + offset_y;
positions[6] = offset_z - offset_x + offset_y;
positions[7] = offset_z - offset_x - offset_y;
}
else
{
// Face triangles
count = 6;
positions[0] = offset_z - offset_x - offset_y;
positions[1] = offset_z + offset_x - offset_y;
positions[2] = offset_z + offset_x + offset_y;
positions[3] = offset_z + offset_x + offset_y;
positions[4] = offset_z - offset_x + offset_y;
positions[5] = offset_z - offset_x - offset_y;
}
// Add the vertices
for (int i = 0; i < count; ++i)
{
mat.at<float>(next_row, 0) = positions[i](0);
mat.at<float>(next_row, 1) = positions[i](1);
mat.at<float>(next_row, 2) = positions[i](2);
mat.at<float>(next_row, 3) = color(0);
mat.at<float>(next_row, 4) = color(1);
mat.at<float>(next_row, 5) = color(2);
if (mode == RENDER_SHADING)
{
mat.at<float>(next_row, 6) = normal(0);
mat.at<float>(next_row, 7) = normal(1);
mat.at<float>(next_row, 8) = normal(2);
}
next_row += 1;
}
}
}
// Generates vertices for a plane.
static void generatePlane(Mat& mat, const Vec2f& size, const Vec3f& color, RenderMode mode)
{
if (mode == RENDER_SHADING)
mat.create(6, 9, CV_32F); // 6 vertices, and 9 floats per vertex (position + color + normal)
else if (mode == RENDER_SIMPLE)
mat.create(6, 6, CV_32F); // 6 vertices, and 6 floats per vertex (position + color)
else if (mode == RENDER_WIREFRAME)
mat.create(8, 6, CV_32F); // 8 vertices, and 6 floats per vertex (position + color)
int next_row = 0;
// Get offset vectors
Vec3f offset_x = Vec3f(size(0), 0.0f, 0.0f);
Vec3f offset_y = Vec3f(0.0f, 0.0f, size(1));
// Generate vertex positions
Vec3f positions[8];
int count = 0;
if (mode == RENDER_WIREFRAME) {
// Quad line segments
count = 8;
positions[0] = - offset_x - offset_y;
positions[1] = offset_x - offset_y;
positions[2] = offset_x - offset_y;
positions[3] = offset_x + offset_y;
positions[4] = offset_x + offset_y;
positions[5] = - offset_x + offset_y;
positions[6] = - offset_x + offset_y;
positions[7] = - offset_x - offset_y;
}
else
{
// Quad triangles
count = 6;
positions[0] = - offset_x - offset_y;
positions[1] = offset_x - offset_y;
positions[2] = offset_x + offset_y;
positions[3] = offset_x + offset_y;
positions[4] = - offset_x + offset_y;
positions[5] = - offset_x - offset_y;
}
// Add the vertices
for (int i = 0; i < count; ++i)
{
mat.at<float>(next_row, 0) = positions[i](0);
mat.at<float>(next_row, 1) = positions[i](1);
mat.at<float>(next_row, 2) = positions[i](2);
mat.at<float>(next_row, 3) = color(0);
mat.at<float>(next_row, 4) = color(1);
mat.at<float>(next_row, 5) = color(2);
if (mode == RENDER_SHADING)
{
mat.at<float>(next_row, 6) = 0.0f;
mat.at<float>(next_row, 7) = 1.0f;
mat.at<float>(next_row, 8) = 0.0f;
}
next_row += 1;
}
}
#endif // HAVE_OPENGL
void setPerspective(const String& win_name, float fov, float z_near, float z_far)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(fov);
CV_UNUSED(z_near);
CV_UNUSED(z_far);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
win->getView().setPerspective(fov, z_near, z_far);
updateWindow(win_name);
#endif
}
void setGridVisible(const String& win_name, bool visible)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(visible);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
win->setGridVisible(visible);
updateWindow(win_name);
#endif
}
void setSun(const String& win_name, const Vec3f& direction, const Vec3f& ambient, const Vec3f& diffuse)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(direction);
CV_UNUSED(ambient);
CV_UNUSED(diffuse);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
win->setSun(direction, ambient, diffuse);
updateWindow(win_name);
#endif
}
void setSky(const String& win_name, const Vec3f& color)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(color);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
win->setSky(color);
updateWindow(win_name);
#endif
}
void showBox(const String& win_name, const String& obj_name, const Vec3f& size, const Vec3f& color, RenderMode mode)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(size);
CV_UNUSED(color);
CV_UNUSED(mode);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Mat mat;
generateBox(mat, size, color, mode);
if (mode == RENDER_WIREFRAME)
showLines(win_name, obj_name, mat);
else
showMesh(win_name, obj_name, mat);
#endif
}
void showPlane(const String& win_name, const String& obj_name, const Vec2f& size, const Vec3f& color, RenderMode mode)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(size);
CV_UNUSED(color);
CV_UNUSED(mode);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Mat mat;
generatePlane(mat, size, color, mode);
if (mode == RENDER_WIREFRAME)
showLines(win_name, obj_name, mat);
else
showMesh(win_name, obj_name, mat);
#endif
}
void showSphere(const String& win_name, const String& obj_name, float radius, const Vec3f& color, RenderMode mode, int divs)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(radius);
CV_UNUSED(color);
CV_UNUSED(mode);
CV_UNUSED(divs);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
CV_Assert(divs >= 1);
if (mode == RENDER_WIREFRAME)
{
static const float PI = 3.14159265359f;
static const float LIMIT = 2.0f * PI;
std::vector<float> points_data;
points_data.reserve(3 * divs * 4 * 2 * 6); // 3 axis, divs * 4 segments per axis, 2 points per segment, 6 values per point
for (int e = 0; e < 3; ++e)
{
auto ex = Vec3f::zeros();
auto ey = Vec3f::zeros();
ex((e + 0) % 3) = radius;
ey((e + 1) % 3) = radius;
for (float t = 0.0f, n; t < LIMIT;)
{
n = t + LIMIT / (divs * 4);
auto p1 = ex * cosf(t) + ey * sinf(t);
auto p2 = ex * cosf(n) + ey * sinf(n);
points_data.insert(points_data.end(), {
p1(0), p1(1), p1(2), color(0), color(1), color(2),
p2(0), p2(1), p2(2), color(0), color(1), color(2),
});
t = n;
}
}
const Mat points_mat = Mat(Size(6, static_cast<int>(points_data.size() / 6)), CV_32F, points_data.data());
showLines(win_name, obj_name, points_mat);
}
else
{
std::vector<float> verts_data;
if (mode == RENDER_SHADING)
verts_data.reserve(6 * divs * divs * 4 * 9); // 6 sides divs * divs * 4 quads per side, 6 vertices per face, 9 values per vertex
else
verts_data.reserve(6 * divs * divs * 4 * 6); // 6 sides, divs * divs * 4 quads per side, 6 vertices per face, 6 values per vertex
for (int s = -1; s <= 1; s += 2)
for (int e = 0; e < 3; ++e)
{
auto ex = Vec3f::all(0.0f);
auto ey = Vec3f::all(0.0f);
auto pz = Vec3f::all(0.0f);
ex((e + 1) % 3) = 1.0f / divs;
ey((e + 2) % 3) = 1.0f / divs;
pz(e) = s;
for (int x = -divs; x < divs; ++x)
for (int y = -divs; y < divs; ++y)
{
// Quad positions
Vec3f positions[6] = {
(x + 0) * ex + (y + 0) * ey + pz,
(x + 0) * ex + (y + 1) * ey + pz,
(x + 1) * ex + (y + 1) * ey + pz,
(x + 1) * ex + (y + 1) * ey + pz,
(x + 1) * ex + (y + 0) * ey + pz,
(x + 0) * ex + (y + 0) * ey + pz,
};
for (int i = 0; i < 6; ++i) {
verts_data.insert(verts_data.end(), {
positions[i](0), positions[i](1), positions[i](2),
color(0), color(1), color(2),
});
if (mode == RENDER_SHADING)
verts_data.insert(verts_data.end(), { 0.0f, 0.0f, 0.0f });
}
}
}
if (mode == RENDER_SIMPLE)
{
for (int i = 0; i < (int)(verts_data.size() / 6); ++i)
{
float l = sqrtf(verts_data[6 * i + 0] * verts_data[6 * i + 0] + verts_data[6 * i + 1] * verts_data[6 * i + 1] + verts_data[6 * i + 2] * verts_data[6 * i + 2]);
float r = radius / l;
verts_data[6 * i + 0] *= r;
verts_data[6 * i + 1] *= r;
verts_data[6 * i + 2] *= r;
}
const Mat verts_mat = Mat(Size(6, static_cast<int>(verts_data.size() / 6)), CV_32F, verts_data.data());
showMesh(win_name, obj_name, verts_mat);
}
else
{
for (int i = 0; i < (int)(verts_data.size() / 9); ++i)
{
float l = sqrtf(verts_data[9 * i + 0] * verts_data[9 * i + 0] + verts_data[9 * i + 1] * verts_data[9 * i + 1] + verts_data[9 * i + 2] * verts_data[9 * i + 2]);
float r = radius / l;
verts_data[9 * i + 6] = verts_data[9 * i + 0] / l;
verts_data[9 * i + 7] = verts_data[9 * i + 1] / l;
verts_data[9 * i + 8] = verts_data[9 * i + 2] / l;
verts_data[9 * i + 0] *= r;
verts_data[9 * i + 1] *= r;
verts_data[9 * i + 2] *= r;
}
const Mat verts_mat = Mat(Size(9, static_cast<int>(verts_data.size() / 9)), CV_32F, verts_data.data());
showMesh(win_name, obj_name, verts_mat);
}
}
#endif
}
void showCameraTrajectory(
const String& win_name, const String& obj_name, InputArray trajectory,
float aspect, float scale, Vec3f frustum_color, Vec3f line_color)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(trajectory);
CV_UNUSED(aspect);
CV_UNUSED(scale);
CV_UNUSED(frustum_color);
CV_UNUSED(line_color);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
CV_Assert(trajectory.dims() == 2 && trajectory.cols() == 6 && trajectory.depth() == CV_32F);
auto data = trajectory.getMat();
std::vector<float> points_data;
points_data.reserve(data.rows * 12 * 2 * 6); // data.rows frustums, 12 lines per frustum, 2 points per line, 6 values per point
// Add frustums
for (int i = 0; i < data.rows; ++i)
{
Vec3f position = { data.at<float>(i, 0), data.at<float>(i, 1), data.at<float>(i, 2) };
Vec3f forward = normalize(Vec3f { data.at<float>(i, 3), data.at<float>(i, 4), data.at<float>(i, 5) });
// Avoid a zero 'right' when forward is (anti)parallel to world up.
Vec3f world_up = (fabsf(forward[1]) > 0.99f) ? Vec3f{0.0f, 0.0f, 1.0f} : Vec3f{0.0f, 1.0f, 0.0f};
Vec3f right = normalize(forward.cross(world_up));
Vec3f up = forward.cross(right);
Vec3f back_f[4] = {
position + (-right * aspect - up) * scale,
position + ( right * aspect - up) * scale,
position + ( right * aspect + up) * scale,
position + (-right * aspect + up) * scale,
};
Vec3f front_f[4] = {
position + (-right * aspect - up) * scale * 1.5f + forward * scale * 2.0f,
position + (right * aspect - up) * scale * 1.5f + forward * scale * 2.0f,
position + (right * aspect + up) * scale * 1.5f + forward * scale * 2.0f,
position + (-right * aspect + up) * scale * 1.5f + forward * scale * 2.0f,
};
// Get line points
Vec3f lines[24] = {
// Back face
back_f[0], back_f[1],
back_f[1], back_f[2],
back_f[2], back_f[3],
back_f[3], back_f[0],
// Front face
front_f[0], front_f[1],
front_f[1], front_f[2],
front_f[2], front_f[3],
front_f[3], front_f[0],
// Side lines
back_f[0], front_f[0],
back_f[1], front_f[1],
back_f[2], front_f[2],
back_f[3], front_f[3],
};
// Add line points
for (int j = 0; j < (int)(sizeof(lines) / sizeof(Vec3f)); ++j)
points_data.insert(points_data.end(), {
lines[j](0), lines[j](1), lines[j](2),
frustum_color[0], frustum_color[1], frustum_color[2],
});
}
// Add trajectory line
for (int i = 1; i < data.rows; ++i)
{
points_data.insert(points_data.end(), {
data.at<float>(i - 1, 0), data.at<float>(i - 1, 1), data.at<float>(i - 1, 2), line_color(0), line_color(1), line_color(2),
data.at<float>(i, 0), data.at<float>(i, 1), data.at<float>(i, 2), line_color(0), line_color(1), line_color(2),
});
}
const Mat points_mat = Mat(Size(6, static_cast<int>(points_data.size() / 6)), CV_32F, points_data.data());
showLines(win_name, obj_name, points_mat);
#endif
}
void showMesh(const String& win_name, const String& obj_name, InputArray verts, InputArray indices)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(verts);
CV_UNUSED(indices);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
setOpenGlContext(win_name);
win->set(obj_name, new Mesh(verts, indices));
updateWindow(win_name);
#endif
}
void showMesh(const String& win_name, const String& obj_name, InputArray verts)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(verts);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
setOpenGlContext(win_name);
win->set(obj_name, new Mesh(verts));
updateWindow(win_name);
#endif
}
void showPoints(const String& win_name, const String& obj_name, InputArray points)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(points);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
setOpenGlContext(win_name);
win->set(obj_name, new PointCloud(points));
updateWindow(win_name);
#endif
}
void showRGBD(const String& win_name, const String& obj_name, InputArray img, const Matx33f& intrinsics, float scale)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(img);
CV_UNUSED(intrinsics);
CV_UNUSED(scale);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
CV_Assert(img.dims() == 2 && img.channels() == 4 && img.type() == CV_32FC4);
Mat mat = img.getMat();
// This section (RGBD to point cloud) should be changed to use the 3d module when
// #20013 is merged.
float fx = intrinsics(0, 0);
float fy = intrinsics(1, 1);
float cx = intrinsics(0, 2);
float cy = intrinsics(1, 2);
// Pre-size and fill row-major (avoids per-pixel Mat reallocation).
Mat points(mat.rows * mat.cols, 6, CV_32F);
for (int v = 0; v < mat.rows; ++v)
for (int u = 0; u < mat.cols; ++u)
{
Vec4f c = mat.at<Vec4f>(v, u);
float d = c(3) * 0.001f; // mm to m
float x_over_z = (cx - static_cast<float>(u)) / fx;
float y_over_z = (cy - static_cast<float>(v)) / fy;
float z = d;
float x = x_over_z * z;
float y = y_over_z * z;
float* p = points.ptr<float>(v * mat.cols + u);
p[0] = x * scale; p[1] = y * scale; p[2] = z * scale;
p[3] = c(0) / 255.0f; p[4] = c(1) / 255.0f; p[5] = c(2) / 255.0f;
}
showPoints(win_name, obj_name, points);
#endif
}
void showLines(const String& win_name, const String& obj_name, InputArray points)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(points);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
setOpenGlContext(win_name);
win->set(obj_name, new Lines(points));
updateWindow(win_name);
#endif
}
void setObjectPosition(const String& win_name, const String& obj_name, const Vec3f& position)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(position);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
Object* obj = win->get(obj_name);
if (!obj)
CV_Error(cv::Error::StsObjectNotFound, "Object not found");
obj->setPosition(position);
updateWindow(win_name);
#endif
}
void setObjectRotation(const String& win_name, const String& obj_name, const Vec3f& rotation)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_UNUSED(rotation);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
Object* obj = win->get(obj_name);
if (!obj)
CV_Error(cv::Error::StsObjectNotFound, "Object not found");
obj->setRotation(rotation);
updateWindow(win_name);
#endif
}
void destroyObject(const String& win_name, const String& obj_name)
{
CV_TRACE_FUNCTION();
#ifndef HAVE_OPENGL
CV_UNUSED(win_name);
CV_UNUSED(obj_name);
CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support");
#else
Window* win = getWindow(win_name);
win->set(obj_name, nullptr);
updateWindow(win_name);
#endif
}
#ifdef HAVE_OPENGL
View::View()
{
this->origin = { 0.0f, 0.0f, 0.0f };
this->distance = 10.0f;
this->position = { 0.0f, 0.0f, this->distance };
this->up = { 0.0f, 1.0f, 0.0f };
this->aspect = 1.0f;
this->setPerspective(1.3f, 0.1f, 2000.0f);
this->lookAt(this->origin, { 0.0f, 1.0f, 0.0f });
}
void View::setAspect(float aspect_)
{
if (this->aspect != aspect_)
{
this->aspect = aspect_;
this->setPerspective(this->fov, this->z_near, this->z_far);
}
}
void View::setPerspective(float fov_, float z_near_, float z_far_)
{
this->fov = fov_;
this->z_near = z_near_;
this->z_far = z_far_;
float tan_half_fovy = ::tan(this->fov / 2.0f);
this->proj = Matx44f::zeros();
this->proj(0, 0) = 1.0f / (this->aspect * tan_half_fovy);
this->proj(1, 1) = 1.0f / tan_half_fovy;
this->proj(2, 2) = (this->z_far + this->z_near) / (this->z_far - this->z_near);
this->proj(2, 3) = 1.0f;
this->proj(3, 2) = -(2.0f * this->z_far * this->z_near) / (this->z_far - this->z_near);
}
void View::rotate(float dx, float dy)
{
this->position = normalize(this->position - this->origin);
float theta = atan2(this->position(2), this->position(0));
float phi = ::asin(this->position(1));
theta -= dx * 0.05f;
phi += dy * 0.05f;
phi = max(-1.5f, min(1.5f, phi));
this->position(0) = ::cos(theta) * ::cos(phi) * this->distance;
this->position(1) = ::sin(phi) * this->distance;
this->position(2) = ::sin(theta) * ::cos(phi) * this->distance;
this->position += this->origin;
this->lookAt(this->origin, this->up);
}
void View::move(float dx, float dy)
{
Vec3f forward = normalize(this->position - this->origin);
Vec3f right = normalize(this->up.cross(forward));
Vec3f up_v = right.cross(forward);
Vec3f delta = normalize(right * dx - up_v * dy) * this->distance * 0.01f;
this->origin += delta;
this->position += delta;
this->lookAt(this->origin, this->up);
}
void View::scaleDistance(float amount)
{
this->distance *= amount;
this->distance = max(0.1f, this->distance);
this->position = normalize(this->position - this->origin) * this->distance + this->origin;
this->lookAt(this->origin, this->up);
}
void View::lookAt(const Vec3f& point, const Vec3f& up_)
{
Vec3f f = normalize(point - this->position);
Vec3f s = normalize(up_.cross(f));
Vec3f u = f.cross(s);
this->view = Matx44f(s(0), u(0), f(0), 0.0f,
s(1), u(1), f(1), 0.0f,
s(2), u(2), f(2), 0.0f,
-s.dot(this->position), -u.dot(this->position), -f.dot(this->position), 1.0f);
}
Window::Window(const String& name_)
{
this->name = name_;
this->sun.direction = normalize(Vec3f(0.3f, 1.0f, 0.5f));
this->sun.ambient = { 0.1f, 0.1f, 0.1f };
this->sun.diffuse = { 1.0f, 1.0f, 1.0f };
this->sky_color = { 0.0f, 0.0f, 0.0f };
float points[] = {
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f,
};
Mat points_mat = Mat(Size(6, 6), CV_32F, points);
this->crosshair = new Lines(points_mat);
this->shaders[this->crosshair->getShaderName()] = this->crosshair->buildShader();
this->crosshair->setShader(this->shaders[this->crosshair->getShaderName()]);
this->grid = nullptr;
}
Window::~Window()
{
delete this->crosshair;
if (this->grid)
delete this->grid;
for (auto obj : this->objects)
delete obj.second;
}
Object* Window::get(const String& obj_name)
{
auto it = this->objects.find(obj_name);
if (it == this->objects.end())
return nullptr;
return it->second;
}
void Window::set(const String& obj_name, Object* obj)
{
auto it = this->objects.find(obj_name);
if (it != this->objects.end() && it->second != obj)
{
delete it->second;
if (obj == nullptr)
this->objects.erase(it);
else
it->second = obj;
}
else if (obj)
this->objects[obj_name] = obj;
if (obj)
{
String shaderName = obj->getShaderName();
auto sit = this->shaders.find(shaderName);
if (sit == this->shaders.end())
this->shaders[shaderName] = obj->buildShader();
obj->setShader(this->shaders[shaderName]);
}
}
void Window::setSun(const Vec3f& direction, const Vec3f& ambient, const Vec3f& diffuse)
{
this->sun.direction = normalize(direction);
this->sun.ambient = ambient;
this->sun.diffuse = diffuse;
}
void Window::setSky(const Vec3f& color)
{
this->sky_color = color;
}
static Mat getGridVertices(const View& view)
{
const Vec3f grid_color = { 0.5f, 0.5f, 0.5f };
const Vec3f center = view.getOrigin();
const Vec3f camera_dir = view.getOrigin() - view.getPosition();
const float scale = 0.3f;
const float tick_step = detail::gridTickStep(view.getDistance() * scale);
// Accumulate line vertices (6 floats each: xyz + rgb), then build one Mat --
// avoids the per-frame Mat::push_back reallocation on the render thread.
std::vector<float> verts;
verts.reserve(4096 * 6);
auto addRows = [&](const float* d, int rows) { verts.insert(verts.end(), d, d + rows * 6); };
float face_sign[3];
const Vec3f min_p = center - Vec3f(1.0f, 1.0f, 1.0f) * view.getDistance() * scale;
const Vec3f max_p = center + Vec3f(1.0f, 1.0f, 1.0f) * view.getDistance() * scale;
// For each axis add a grid
for (int ai = 0; ai < 3; ++ai)
{
Vec3f az = { 0.0f, 0.0f, 0.0f };
az((ai + 2) % 3) = 1.0f;
// Check if face is positive or negative along the az axis
face_sign[ai] = camera_dir.dot(az) > 0.0f ? 1.0f : -1.0f;
float x = (floor(min_p(ai) / tick_step) + 1.0f) * tick_step;
for (; x < max_p(ai); x += tick_step)
{
Vec3f a = min_p;
Vec3f b = max_p;
a((ai + 0) % 3) = x;
b((ai + 0) % 3) = x;
if (face_sign[ai] > 0.0f)
a((ai + 2) % 3) = b((ai + 2) % 3);
else
b((ai + 2) % 3) = a((ai + 2) % 3);
float data[] = {
a(0), a(1), a(2), grid_color(0), grid_color(1), grid_color(2),
b(0), b(1), b(2), grid_color(0), grid_color(1), grid_color(2),
};
addRows(data, 2);
}
float y = (floor(min_p((ai + 1) % 3) / tick_step) + 1.0f) * tick_step;
for (; y < max_p((ai + 1) % 3); y += tick_step)
{
Vec3f a = min_p;
Vec3f b = max_p;
a((ai + 1) % 3) = y;
b((ai + 1) % 3) = y;
if (face_sign[ai] > 0.0f)
a((ai + 2) % 3) = b((ai + 2) % 3);
else
b((ai + 2) % 3) = a((ai + 2) % 3);
float data[] = {
a(0), a(1), a(2), grid_color(0), grid_color(1), grid_color(2),
b(0), b(1), b(2), grid_color(0), grid_color(1), grid_color(2),
};
addRows(data, 2);
}
}
// Draw Ox, Oy and Oz axes and ticks
{
Vec3f a = { face_sign[1] > 0.0f ? min_p(0) : max_p(0), face_sign[2] > 0.0f ? max_p(1) : min_p(1), face_sign[0] > 0.0f ? max_p(2) : min_p(2) };
Vec3f b = { face_sign[1] > 0.0f ? min_p(0) : max_p(0), face_sign[2] > 0.0f ? max_p(1) : min_p(1), face_sign[0] > 0.0f ? min_p(2) : max_p(2) };
Vec3f c = { face_sign[1] > 0.0f ? max_p(0) : min_p(0), face_sign[2] > 0.0f ? max_p(1) : min_p(1), face_sign[0] > 0.0f ? min_p(2) : max_p(2) };
Vec3f d = { face_sign[1] > 0.0f ? max_p(0) : min_p(0), face_sign[2] > 0.0f ? min_p(1) : max_p(1), face_sign[0] > 0.0f ? min_p(2) : max_p(2) };
float data[] = {
a(0), a(1), a(2), 0.0f, 0.0f, 0.8f,
b(0), b(1), b(2), 0.0f, 0.0f, 0.8f,
b(0), b(1), b(2), 0.8f, 0.0f, 0.0f,
c(0), c(1), c(2), 0.8f, 0.0f, 0.0f,
c(0), c(1), c(2), 0.0f, 0.8f, 0.0f,
d(0), d(1), d(2), 0.0f, 0.8f, 0.0f,
};
addRows(data, 6);
float x = (floor(min_p(0) / tick_step) + 1.0f) * tick_step;
for (; x < max_p(0); x += tick_step)
{
Vec3f la, lb;
la(0) = lb(0) = x;
la(1) = lb(1) = face_sign[2] > 0.0f ? max_p(1) : min_p(1);
la(2) = face_sign[0] > 0.0f ? min_p(2) : max_p(2);
lb(2) = la(2) - face_sign[0] * 0.03f * scale * view.getDistance();
float line[] = {
la(0), la(1), la(2), 0.8f, 0.0f, 0.0f,
lb(0), lb(1), lb(2), 0.8f, 0.0f, 0.0f,
};
addRows(line, 2);
}
float y = (floor(min_p(1) / tick_step) + 1.0f) * tick_step;
for (; y < max_p(1); y += tick_step)
{
Vec3f la, lb;
la(0) = lb(0) = face_sign[1] > 0.0f ? max_p(0) : min_p(0);
la(1) = lb(1) = y;
la(2) = face_sign[0] > 0.0f ? min_p(2) : max_p(2);
lb(2) = la(2) - face_sign[0] * 0.03f * scale * view.getDistance();
float line[] = {
la(0), la(1), la(2), 0.0f, 0.8f, 0.0f,
lb(0), lb(1), lb(2), 0.0f, 0.8f, 0.0f,
};
addRows(line, 2);
}
float z = (floor(min_p(2) / tick_step) + 1.0f) * tick_step;
for (; z < max_p(2); z += tick_step)
{
Vec3f la, lb;
la(0) = face_sign[1] > 0.0f ? min_p(0) : max_p(0);
lb(0) = la(0) - face_sign[1] * 0.03f * scale * view.getDistance();
la(1) = lb(1) = face_sign[2] > 0.0f ? max_p(1) : min_p(1);
la(2) = lb(2) = z;
float line[] = {
la(0), la(1), la(2), 0.0f, 0.0f, 0.8f,
lb(0), lb(1), lb(2), 0.0f, 0.0f, 0.8f,
};
addRows(line, 2);
}
}
if (verts.empty())
return Mat();
return Mat((int)(verts.size() / 6), 6, CV_32F, verts.data()).clone();
}
void Window::setGridVisible(bool visible)
{
if (visible)
{
this->grid = new Lines(Mat(4096, 6, CV_32F), 0);
this->grid->setShader(this->shaders[this->grid->getShaderName()]);
}
else if (this->grid)
{
delete this->grid;
this->grid = nullptr;
}
}
void Window::draw()
{
Rect rect = getWindowImageRect(this->name);
float aspect = static_cast<float>(rect.width) / static_cast<float>(rect.height);
this->view.setAspect(aspect);
ogl::enable(ogl::DEPTH_TEST);
ogl::clearColor(this->sky_color.mul(255.0f));
if (this->grid)
{
static_cast<Lines*>(this->grid)->update(getGridVertices(this->view));
this->grid->draw(this->view, this->sun);
}
else
{
this->crosshair->setPosition(this->view.getOrigin());
this->crosshair->draw(this->view, this->sun);
}
for (auto& obj : this->objects)
obj.second->draw(this->view, this->sun);
}
void Window::onMouse(int event, int x, int y, int flags)
{
if (event == EVENT_LBUTTONDOWN || event == EVENT_RBUTTONDOWN)
{
this->l_mouse_x = x;
this->l_mouse_y = y;
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
this->view.rotate(x - this->l_mouse_x, y - this->l_mouse_y);
updateWindow(this->name);
this->l_mouse_x = x;
this->l_mouse_y = y;
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_RBUTTON))
{
this->view.move(x - this->l_mouse_x, y - this->l_mouse_y);
updateWindow(this->name);
this->l_mouse_x = x;
this->l_mouse_y = y;
}
else if (event == EVENT_MOUSEWHEEL)
{
this->view.scaleDistance(min(1.2f, max(0.8f, 1.0f - getMouseWheelDelta(flags) / 12.0f)));
updateWindow(this->name);
}
}
Object::Object()
{
this->position = { 0.0f, 0.0f, 0.0f };
this->rotation = { 0.0f, 0.0f, 0.0f };
this->model = Matx44f::eye();
}
void Object::setPosition(const Vec3f& position_)
{
this->position = position_;
this->updateModel();
}
void Object::setRotation(const Vec3f& rotation_)
{
this->rotation = rotation_;
this->updateModel();
}
void Object::updateModel()
{
// Calculate rotation matrices
Matx44f rot_a = Matx44f::eye();
rot_a(0, 0) = ::cos(this->rotation(0));
rot_a(1, 0) = ::sin(this->rotation(0));
rot_a(0, 1) = -::sin(this->rotation(0));
rot_a(1, 1) = ::cos(this->rotation(0));
Matx44f rot_b = Matx44f::eye();
rot_b(1, 1) = ::cos(this->rotation(1));
rot_b(2, 1) = ::sin(this->rotation(1));
rot_b(1, 2) = -::sin(this->rotation(1));
rot_b(2, 2) = ::cos(this->rotation(1));
Matx44f rot_c = Matx44f::eye();
rot_c(0, 0) = ::cos(this->rotation(2));
rot_c(2, 0) = ::sin(this->rotation(2));
rot_c(0, 2) = -::sin(this->rotation(2));
rot_c(2, 2) = ::cos(this->rotation(2));
// Calculate translation matrix
Matx44f trans = Matx44f::eye();
trans(3, 0) = this->position(0);
trans(3, 1) = this->position(1);
trans(3, 2) = this->position(2);
// Multiply matrices
this->model = rot_c * rot_b * rot_a * trans;
}
Mesh::Mesh(InputArray verts_, InputArray indices_)
{
// Check parameter validity
CV_Assert(verts_.channels() == 1 && verts_.dims() == 2 && (verts_.size().width == 3 || verts_.size().width == 6 || verts_.size().width == 9));
CV_Assert(verts_.depth() == CV_32F);
CV_Assert(indices_.channels() == 1 && indices_.dims() == 2 && indices_.size().width == 3);
CV_Assert(indices_.depth() == CV_8U || indices_.depth() == CV_16U || indices_.depth() == CV_32S);
// Prepare buffers
if (verts_.kind() == _InputArray::OPENGL_BUFFER)
this->verts = verts_.getOGlBuffer();
else
this->verts.copyFrom(verts_, ogl::Buffer::ARRAY_BUFFER);
if (indices_.kind() == _InputArray::OPENGL_BUFFER)
this->indices = indices_.getOGlBuffer();
else
this->indices.copyFrom(indices_, ogl::Buffer::ELEMENT_ARRAY_BUFFER);
switch (indices_.depth())
{
case CV_8U:
this->index_type = ogl::UNSIGNED_BYTE;
break;
case CV_16U:
this->index_type = ogl::UNSIGNED_SHORT;
break;
case CV_32S:
this->index_type = ogl::UNSIGNED_INT;
break;
}
// Prepare vertex array
this->initVA(verts_.size().width);
}
Mesh::Mesh(InputArray verts_)
{
// Check parameter validity
CV_Assert(verts_.channels() == 1 && verts_.dims() == 2 && (verts_.size().width == 3 || verts_.size().width == 6 || verts_.size().width == 9));
CV_Assert(verts_.depth() == CV_32F);
// Prepare buffers
if (verts_.kind() == _InputArray::OPENGL_BUFFER)
this->verts = verts_.getOGlBuffer();
else
this->verts.copyFrom(verts_, ogl::Buffer::ARRAY_BUFFER);
this->index_type = 0;
// Prepare vertex array
this->initVA(verts_.size().width);
}
void Mesh::initVA(int width)
{
// Prepare vertex array
if (width == 3)
{
this->va.create({
{
this->verts,
3 * sizeof(float), 0,
3, ogl::Attribute::FLOAT,
false, false,
0
}
});
}
else if (width == 6)
{
this->va.create({
{
this->verts,
6 * sizeof(float), 0,
3, ogl::Attribute::FLOAT,
false, false,
0
},
{
this->verts,
6 * sizeof(float), 3 * sizeof(float),
3, ogl::Attribute::FLOAT,
false, false,
1
}
});
}
else if (width == 9)
{
this->va.create({
{
this->verts,
9 * sizeof(float), 0,
3, ogl::Attribute::FLOAT,
false, false,
0
},
{
this->verts,
9 * sizeof(float), 3 * sizeof(float),
3, ogl::Attribute::FLOAT,
false, false,
1
},
{
this->verts,
9 * sizeof(float), 6 * sizeof(float),
3, ogl::Attribute::FLOAT,
false, false,
2
}
});
}
}
void Mesh::draw(const View& view, const Light& light)
{
this->program.bind();
this->va.bind();
ogl::Program::setUniformMat4x4(this->model_loc, this->getModel());
ogl::Program::setUniformMat4x4(this->view_loc, view.getView());
ogl::Program::setUniformMat4x4(this->proj_loc, view.getProj());
if (this->sun_direction_loc != -1)
{
ogl::Program::setUniformVec3(this->sun_direction_loc, light.direction);
ogl::Program::setUniformVec3(this->sun_ambient_loc, light.ambient);
ogl::Program::setUniformVec3(this->sun_diffuse_loc, light.diffuse);
}
if (this->index_type == 0)
ogl::drawArrays(0, this->verts.size().height, ogl::TRIANGLES);
else
{
this->indices.bind(ogl::Buffer::ELEMENT_ARRAY_BUFFER);
ogl::drawElements(0, this->indices.size().area(), this->index_type, ogl::TRIANGLES);
}
}
String Mesh::getShaderName()
{
if (this->verts.size().width == 3)
return "mesh-xyz";
else if (this->verts.size().width == 6)
return "mesh-xyz-rgb";
else
return "mesh-xyz-rgb-uvw";
}
ogl::Program Mesh::buildShader()
{
ogl::Shader vs, fs;
// Setup shader pipeline
if (this->verts.size().width == 3)
{
vs = ogl::Shader(R"(
#version 330 core
layout (location = 0) in vec3 vert_pos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main() {
gl_Position = vec4(vert_pos, 1.0) * model * view * proj;
}
)", ogl::Shader::VERTEX);
fs = ogl::Shader(R"(
#version 330 core
out vec4 frag_color;
void main() {
frag_color = vec4(1.0, 1.0, 1.0, 1.0);
}
)", ogl::Shader::FRAGMENT);
}
else if (this->verts.size().width == 6)
{
vs = ogl::Shader(R"(
#version 330 core
layout (location = 0) in vec3 vert_pos;
layout (location = 1) in vec3 vert_color;
out vec3 frag_color;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main() {
frag_color = vert_color;
gl_Position = vec4(vert_pos, 1.0) * model * view * proj;
}
)", ogl::Shader::VERTEX);
fs = ogl::Shader(R"(
#version 330 core
in vec3 frag_color;
out vec4 color;
void main() {
color = vec4(frag_color, 1.0);
}
)", ogl::Shader::FRAGMENT);
}
else if (this->verts.size().width == 9)
{
vs = ogl::Shader(R"(
#version 330 core
layout (location = 0) in vec3 vert_pos;
layout (location = 1) in vec3 vert_color;
layout (location = 2) in vec3 vert_normal;
out vec3 frag_color;
out vec3 frag_normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main() {
frag_color = vert_color;
frag_normal = vert_normal * mat3(transpose(inverse(model)));
gl_Position = vec4(vert_pos, 1.0) * model * view * proj;
}
)", ogl::Shader::VERTEX);
fs = ogl::Shader(R"(
#version 330 core
in vec3 frag_color;
in vec3 frag_normal;
out vec4 color;
uniform vec3 sun_direction;
uniform vec3 sun_ambient;
uniform vec3 sun_diffuse;
void main() {
float diff = max(dot(frag_normal, sun_direction), 0.0);
vec3 ambient = frag_color * sun_ambient;
vec3 diffuse = frag_color * diff * sun_diffuse;
color = vec4(ambient + diffuse, 1.0);
}
)", ogl::Shader::FRAGMENT);
}
return ogl::Program(vs, fs);
}
void Mesh::setShader(ogl::Program program_)
{
this->program = program_;
this->model_loc = this->program.getUniformLocation("model");
this->view_loc = this->program.getUniformLocation("view");
this->proj_loc = this->program.getUniformLocation("proj");
this->sun_direction_loc = -1;
if (this->verts.size().width == 9)
{
this->sun_direction_loc = this->program.getUniformLocation("sun_direction");
this->sun_ambient_loc = this->program.getUniformLocation("sun_ambient");
this->sun_diffuse_loc = this->program.getUniformLocation("sun_diffuse");
}
}
Lines::Lines(InputArray points_, int count_)
{
// Check parameter validity
CV_Assert(points_.channels() == 1 && points_.dims() == 2 && points_.size().width == 6);
CV_Assert(points_.depth() == CV_32F);
// Prepare buffers
if (points_.kind() == _InputArray::OPENGL_BUFFER)
this->points = points_.getOGlBuffer();
else
{
this->points.create(points_.size(), points_.type(), ogl::Buffer::ARRAY_BUFFER);
if (count_ == -1 || count_ > 0)
this->points.copyFrom(points_, ogl::Buffer::ARRAY_BUFFER);
}
// Prepare vertex array
this->va.create({
{
this->points,
6 * sizeof(float), 0,
3, ogl::Attribute::FLOAT,
false, false,
0
},
{
this->points,
6 * sizeof(float), 3 * sizeof(float),
3, ogl::Attribute::FLOAT,
false, false,
1
}
});
if (count_ == -1)
this->count = this->points.size().height;
else
this->count = count_;
}
void Lines::draw(const View& view, const Light& light)
{
CV_UNUSED(light);
if (this->count > 0)
{
this->program.bind();
this->va.bind();
ogl::Program::setUniformMat4x4(this->model_loc, this->getModel());
ogl::Program::setUniformMat4x4(this->view_loc, view.getView());
ogl::Program::setUniformMat4x4(this->proj_loc, view.getProj());
ogl::drawArrays(0, this->count, ogl::LINES);
}
}
void Lines::update(InputArray points_)
{
// Check parameter validity
CV_Assert(points_.channels() == 1 && points_.dims() == 2 && points_.size().width == 6);
CV_Assert(points_.depth() == CV_32F);
this->points.copyFrom(points_, ogl::Buffer::ARRAY_BUFFER);
this->count = points_.size().height;
}
String Lines::getShaderName()
{
return "lines";
}
ogl::Program Lines::buildShader()
{
// Setup shader pipeline
auto vs = ogl::Shader(R"(
#version 330 core
layout (location = 0) in vec3 vert_pos;
layout (location = 1) in vec3 vert_color;
out vec3 frag_color;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main() {
frag_color = vert_color;
gl_Position = vec4(vert_pos, 1.0) * model * view * proj;
}
)", ogl::Shader::VERTEX);
auto fs = ogl::Shader(R"(
#version 330 core
in vec3 frag_color;
out vec4 color;
void main() {
color = vec4(frag_color, 1.0);
}
)", ogl::Shader::FRAGMENT);
return ogl::Program(vs, fs);
}
void Lines::setShader(ogl::Program program_)
{
this->program = program_;
this->model_loc = this->program.getUniformLocation("model");
this->view_loc = this->program.getUniformLocation("view");
this->proj_loc = this->program.getUniformLocation("proj");
}
PointCloud::PointCloud(InputArray points_)
{
// Check parameter validity
CV_Assert(points_.channels() == 1 && points_.dims() == 2 && points_.size().width == 6);
CV_Assert(points_.depth() == CV_32F);
// Prepare buffers
if (points_.kind() == _InputArray::OPENGL_BUFFER)
this->points = points_.getOGlBuffer();
else
this->points.copyFrom(points_, ogl::Buffer::ARRAY_BUFFER);
// Prepare vertex array
this->va.create({
{
this->points,
6 * sizeof(float), 0,
3, ogl::Attribute::FLOAT,
false, false,
0
},
{
this->points,
6 * sizeof(float), 3 * sizeof(float),
3, ogl::Attribute::FLOAT,
false, false,
1
}
});
}
void PointCloud::draw(const View& view, const Light& light)
{
CV_UNUSED(light);
this->program.bind();
this->va.bind();
ogl::Program::setUniformMat4x4(this->model_loc, this->getModel());
ogl::Program::setUniformMat4x4(this->view_loc, view.getView());
ogl::Program::setUniformMat4x4(this->proj_loc, view.getProj());
ogl::drawArrays(0, this->points.size().height, ogl::POINTS);
}
String PointCloud::getShaderName()
{
return "points";
}
ogl::Program PointCloud::buildShader()
{
// Setup shader pipeline
auto vs = ogl::Shader(R"(
#version 330 core
layout (location = 0) in vec3 vert_pos;
layout (location = 1) in vec3 vert_color;
out vec3 frag_color;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main() {
frag_color = vert_color;
gl_Position = vec4(vert_pos, 1.0) * model * view * proj;
}
)", ogl::Shader::VERTEX);
auto fs = ogl::Shader(R"(
#version 330 core
in vec3 frag_color;
out vec4 color;
void main() {
color = vec4(frag_color, 1.0);
}
)", ogl::Shader::FRAGMENT);
return ogl::Program(vs, fs);
}
void PointCloud::setShader(ogl::Program program_)
{
this->program = program_;
this->model_loc = this->program.getUniformLocation("model");
this->view_loc = this->program.getUniformLocation("view");
this->proj_loc = this->program.getUniformLocation("proj");
}
} // namespace viz3d
} // namespace cv
#endif
+209
View File
@@ -0,0 +1,209 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef OPENCV_PTCLOUD_VIZ3D_PRIVATE_HPP
#define OPENCV_PTCLOUD_VIZ3D_PRIVATE_HPP
#include "../precomp.hpp"
#include "opencv2/core/private.hpp" // HAVE_OPENGL from cvconfig.h
#include "opencv2/core/opengl.hpp"
#include "opencv2/highgui.hpp" // window + OpenGL context (imported from highgui)
#include "opencv2/ptcloud/viz3d.hpp" // public viz3d API declarations
#include <map>
#ifdef HAVE_OPENGL
namespace cv { namespace viz3d {
// Stores a view's matrices
class View
{
public:
View();
void setAspect(float aspect);
void setPerspective(float fov, float z_near, float z_far);
void rotate(float dx, float dy); // Rotates the camera using mouse input
void move(float dx, float dy); // Moves the camera using mouse input
void scaleDistance(float amount);
inline Vec3f getOrigin() const { return this->origin; }
inline Vec3f getPosition() const { return this->position; }
inline float getDistance() const { return this->distance; }
inline Matx44f getView() const { return this->view; }
inline Matx44f getProj() const { return this->proj; }
private:
void lookAt(const Vec3f& point, const Vec3f& up);
float aspect;
float fov;
float z_near;
float z_far;
Matx44f proj;
Matx44f view;
Vec3f origin;
Vec3f position;
Vec3f up;
float distance;
};
// Stores information about a light
struct Light
{
Vec3f direction;
Vec3f ambient;
Vec3f diffuse;
};
// Base class for viz3d objects which can be rendered
class Object
{
public:
Object();
virtual ~Object() = default;
void setPosition(const Vec3f& position);
void setRotation(const Vec3f& rotation);
virtual void draw(const View& view, const Light& light) = 0;
virtual String getShaderName() = 0;
virtual ogl::Program buildShader() = 0;
virtual void setShader(ogl::Program program) = 0;
inline Matx44f getModel() const { return this->model; }
private:
void updateModel();
Vec3f position;
Vec3f rotation;
Matx44f model;
};
// Class which stores the viz3d data associated to a window.
class Window
{
public:
Window(const String& name);
~Window();
Object* get(const String& obj_name);
void set(const String& obj_name, Object* obj);
void setSun(const Vec3f& direction, const Vec3f& ambient, const Vec3f& diffuse);
void setSky(const Vec3f& color);
void setGridVisible(bool visible);
void draw();
void onMouse(int event, int x, int y, int flags);
inline View& getView() { return this->view; }
private:
String name;
Size size;
Light sun;
Vec3f sky_color;
View view;
int l_mouse_x;
int l_mouse_y;
Object* crosshair;
Object* grid;
std::map<String, Object*> objects;
std::map<String, ogl::Program> shaders;
};
// Class which stores the viz3d data associated to a mesh object.
class Mesh : public Object
{
public:
Mesh(InputArray verts, InputArray indices);
Mesh(InputArray verts);
virtual void draw(const View& view, const Light& light) override;
virtual String getShaderName() override;
virtual ogl::Program buildShader() override;
virtual void setShader(ogl::Program program) override;
private:
void initVA(int width);
ogl::Program program;
ogl::VertexArray va;
ogl::Buffer verts;
ogl::Buffer indices;
int index_type;
int model_loc;
int view_loc;
int proj_loc;
int sun_direction_loc;
int sun_ambient_loc;
int sun_diffuse_loc;
};
// Class which stores the viz3d data associated to a lines object.
class Lines : public Object
{
public:
Lines(InputArray points, int count = -1);
virtual void draw(const View& view, const Light& light) override;
void update(InputArray points);
virtual String getShaderName() override;
virtual ogl::Program buildShader() override;
virtual void setShader(ogl::Program program) override;
private:
ogl::Program program;
ogl::VertexArray va;
ogl::Buffer points;
int model_loc;
int view_loc;
int proj_loc;
int count;
};
// Data necessary for drawing a point cloud on a window.
class PointCloud : public Object
{
public:
PointCloud(InputArray points);
virtual void draw(const View& view, const Light& light) override;
virtual String getShaderName() override;
virtual ogl::Program buildShader() override;
virtual void setShader(ogl::Program program) override;
private:
ogl::Program program;
ogl::VertexArray va;
ogl::Buffer points;
int model_loc;
int view_loc;
int proj_loc;
};
}} // namespace cv::viz3d
#endif // HAVE_OPENGL
#endif
+82
View File
@@ -0,0 +1,82 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#include <opencv2/highgui.hpp> // updateWindow / destroyWindow
#include "../src/viz3d/grid_ticks.hpp" // GL-free grid spacing helper
namespace opencv_test { namespace {
using namespace cv;
// Regression for the grid tick_step infinite-loop hang (#1): the old code did
// logf(1.0) == 0 -> division -> inf -> the halving loop never terminated for
// distance*scale > 4. gridTickStep must terminate and stay finite/positive for
// every distance, including the region that used to hang. GL-free (runs in CI).
TEST(Viz3D, grid_tick_step)
{
// Values span below/at/above the old hang threshold (dist_scale > 4).
const float samples[] = { 0.03f, 0.9f, 3.0f, 4.0f, 5.0f, 13.4f, 40.0f, 400.0f, 4000.0f };
for (float ds : samples)
{
float ts = viz3d::detail::gridTickStep(ds);
ASSERT_TRUE(std::isfinite(ts)) << "ds=" << ds;
ASSERT_GT(ts, 0.0f) << "ds=" << ds;
float ratio = ds / ts;
EXPECT_GE(ratio, 2.0f - 1e-3f) << "ds=" << ds << " ratio=" << ratio;
EXPECT_LE(ratio, 4.0f + 1e-3f) << "ds=" << ds << " ratio=" << ratio;
}
}
// viz3d is OpenGL/window based. Skip when no GL context can be created
// (e.g. headless CI without a display).
static bool viz3dAvailable()
{
try
{
viz3d::showPoints("viz3d_gl_probe", "p", Mat::zeros(4, 6, CV_32F));
destroyWindow("viz3d_gl_probe");
return true;
}
catch (const cv::Exception&)
{
return false;
}
}
// Smoke test for the public viz3d API. Builds a scene and forces redraws; passes
// if nothing throws. Exercises the paths touched by review fixes: #3 (showRGBD),
// #4 (degenerate up-vector trajectory), and the grid render path. The #1 hang
// itself is distance-triggered (no public camera-distance setter) and is covered
// by the GL-free Viz3D.grid_tick_step test above.
TEST(Viz3D, render_scene_smoke)
{
if (!viz3dAvailable())
throw cvtest::SkipTestException("viz3d/OpenGL not available (no GL context)");
const String w = "viz3d_test";
Mat pts(256, 6, CV_32F);
randu(pts, 0.0f, 1.0f);
EXPECT_NO_THROW(viz3d::showPoints(w, "pts", pts));
EXPECT_NO_THROW(viz3d::setGridVisible(w, true));
EXPECT_NO_THROW(viz3d::showBox(w, "box", Vec3f::all(1.0f), Vec3f(1, 0, 0)));
EXPECT_NO_THROW(viz3d::showSphere(w, "sphere", 1.0f, Vec3f(0, 1, 0)));
// forward = (0,+1,0) and (0,-1,0): the degenerate up-vector case guarded by #4.
float traj[] = { 0,0,0, 0,1,0, 1,0,0, 0,-1,0 };
EXPECT_NO_THROW(viz3d::showCameraTrajectory(w, "traj", Mat(2, 6, CV_32F, traj), 1.0f, 0.5f));
Mat rgbd(16, 16, CV_32FC4, Scalar(120, 120, 120, 500)); // #3 showRGBD path
EXPECT_NO_THROW(viz3d::showRGBD(w, "rgbd", rgbd, Matx33f(8, 0, 8, 0, 8, 8, 0, 0, 1), 0.1f));
for (int i = 0; i < 4; ++i)
EXPECT_NO_THROW(updateWindow(w));
EXPECT_NO_THROW(viz3d::destroyObject(w, "pts"));
destroyAllWindows();
}
}} // namespace
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB