You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1137 lines
38 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2015 Intel Corporation. All Rights Reserved.
#pragma once
#include <librealsense2/rs.hpp> // Include RealSense Cross Platform API
#define GL_SILENCE_DEPRECATION
#define GLFW_INCLUDE_GLU
#include <GLFW/glfw3.h>
#include <string>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>
#include <map>
#include <functional>
#include "../third-party/stb_easy_font.h"
#include "example-utils.hpp"
#ifndef PI
#define PI 3.14159265358979323846
#define PI_FL 3.141592f
#endif
const float IMU_FRAME_WIDTH = 1280.f;
const float IMU_FRAME_HEIGHT = 720.f;
enum class Priority { high = 0, medium = -1, low = -2 };
//////////////////////////////
// Basic Data Types //
//////////////////////////////
struct float3 {
float x, y, z;
float3 operator*(float t)
{
return { x * t, y * t, z * t };
}
float3 operator-(float t)
{
return { x - t, y - t, z - t };
}
void operator*=(float t)
{
x = x * t;
y = y * t;
z = z * t;
}
void operator=(float3 other)
{
x = other.x;
y = other.y;
z = other.z;
}
void add(float t1, float t2, float t3)
{
x += t1;
y += t2;
z += t3;
}
};
struct float2 { float x, y; };
struct frame_pixel
{
int frame_idx;
float2 pixel;
};
struct rect
{
float x, y;
float w, h;
// Create new rect within original boundaries with give aspect ration
rect adjust_ratio(float2 size) const
{
auto H = static_cast<float>(h), W = static_cast<float>(h) * size.x / size.y;
if (W > w)
{
auto scale = w / W;
W *= scale;
H *= scale;
}
return{ x + (w - W) / 2, y + (h - H) / 2, W, H };
}
};
struct tile_properties
{
unsigned int x, y; //location of tile in the grid
unsigned int w, h; //width and height by number of tiles
Priority priority; //when should the tile be drawn?: high priority is on top of all, medium is a layer under top layer, low is a layer under medium layer
};
//name aliasing the map of pairs<frame, tile_properties>
using frame_and_tile_property = std::pair<rs2::frame, tile_properties>;
using frames_mosaic = std::map<int, frame_and_tile_property>;
//////////////////////////////
// Simple font loading code //
//////////////////////////////
inline void draw_text(int x, int y, const char* text)
{
std::vector<char> buffer;
buffer.resize(60000); // ~300 chars
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 16, &(buffer[0]) );
glDrawArrays( GL_QUADS,
0,
4
* stb_easy_font_print( (float)x,
(float)( y - 7 ),
(char *)text,
nullptr,
&( buffer[0] ),
int( sizeof( char ) * buffer.size() ) ) );
glDisableClientState(GL_VERTEX_ARRAY);
}
void set_viewport(const rect& r)
{
glViewport((int)r.x, (int)r.y, (int)r.w, (int)r.h);
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glOrtho(0, r.w, r.h, 0, -1, +1);
}
class imu_renderer
{
public:
void render(const rs2::motion_frame& frame, const rect& r)
{
draw_motion(frame, r.adjust_ratio({ IMU_FRAME_WIDTH, IMU_FRAME_HEIGHT }));
}
GLuint get_gl_handle() { return _gl_handle; }
private:
GLuint _gl_handle = 0;
void draw_motion(const rs2::motion_frame& f, const rect& r)
{
if (!_gl_handle)
glGenTextures(1, &_gl_handle);
set_viewport(r);
draw_text(int(0.05f * r.w), int(0.05f * r.h), f.get_profile().stream_name().c_str());
auto md = f.get_motion_data();
auto x = md.x;
auto y = md.y;
auto z = md.z;
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(-2.8, 2.8, -2.4, 2.4, -7, 7);
glRotatef(25, 1.0f, 0.0f, 0.0f);
glTranslatef(0, -0.33f, -1.f);
glRotatef(-135, 0.0f, 1.0f, 0.0f);
glRotatef(180, 0.0f, 0.0f, 1.0f);
glRotatef(-90, 0.0f, 1.0f, 0.0f);
draw_axes(1, 2);
draw_circle(1, 0, 0, 0, 1, 0);
draw_circle(0, 1, 0, 0, 0, 1);
draw_circle(1, 0, 0, 0, 0, 1);
const auto canvas_size = 230;
const auto vec_threshold = 0.01f;
float norm = std::sqrt(x * x + y * y + z * z);
if (norm < vec_threshold)
{
const auto radius = 0.05;
static const int circle_points = 100;
static const float angle = 2.0f * 3.1416f / circle_points;
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_POLYGON);
double angle1 = 0.0;
glVertex2d(radius * cos(0.0), radius * sin(0.0));
int i;
for (i = 0; i < circle_points; i++)
{
glVertex2d(radius * cos(angle1), radius * sin(angle1));
angle1 += angle;
}
glEnd();
}
else
{
auto vectorWidth = 3.f;
glLineWidth(vectorWidth);
glBegin(GL_LINES);
glColor3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(x / norm, y / norm, z / norm);
glEnd();
// Save model and projection matrix for later
GLfloat model[16];
glGetFloatv(GL_MODELVIEW_MATRIX, model);
GLfloat proj[16];
glGetFloatv(GL_PROJECTION_MATRIX, proj);
glLoadIdentity();
glOrtho(-canvas_size, canvas_size, -canvas_size, canvas_size, -1, +1);
std::ostringstream s1;
const auto precision = 3;
glRotatef(180, 1.0f, 0.0f, 0.0f);
s1 << "(" << std::fixed << std::setprecision(precision) << x << "," << std::fixed << std::setprecision(precision) << y << "," << std::fixed << std::setprecision(precision) << z << ")";
print_text_in_3d(x, y, z, s1.str().c_str(), false, model, proj, 1 / norm);
std::ostringstream s2;
s2 << std::setprecision(precision) << norm;
print_text_in_3d(x / 2, y / 2, z / 2, s2.str().c_str(), true, model, proj, 1 / norm);
}
glMatrixMode(GL_PROJECTION);
glPopMatrix();
}
//IMU drawing helper functions
void multiply_vector_by_matrix(GLfloat vec[], GLfloat mat[], GLfloat* result)
{
const auto N = 4;
for (int i = 0; i < N; i++)
{
result[i] = 0;
for (int j = 0; j < N; j++)
{
result[i] += vec[j] * mat[N * j + i];
}
}
return;
}
float2 xyz_to_xy(float x, float y, float z, GLfloat model[], GLfloat proj[], float vec_norm)
{
GLfloat vec[4] = { x, y, z, 0 };
float tmp_result[4];
float result[4];
const auto canvas_size = 230;
multiply_vector_by_matrix(vec, model, tmp_result);
multiply_vector_by_matrix(tmp_result, proj, result);
return{ canvas_size * vec_norm * result[0], canvas_size * vec_norm * result[1] };
}
void print_text_in_3d(float x, float y, float z, const char* text, bool center_text, GLfloat model[], GLfloat proj[], float vec_norm)
{
auto xy = xyz_to_xy(x, y, z, model, proj, vec_norm);
auto w = (center_text) ? stb_easy_font_width((char*)text) : 0;
glColor3f(1.0f, 1.0f, 1.0f);
draw_text((int)(xy.x - w / 2), (int)xy.y, text);
}
static void draw_axes(float axis_size = 1.f, float axisWidth = 4.f)
{
// Triangles For X axis
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(axis_size * 1.1f, 0.f, 0.f);
glVertex3f(axis_size, -axis_size * 0.05f, 0.f);
glVertex3f(axis_size, axis_size * 0.05f, 0.f);
glVertex3f(axis_size * 1.1f, 0.f, 0.f);
glVertex3f(axis_size, 0.f, -axis_size * 0.05f);
glVertex3f(axis_size, 0.f, axis_size * 0.05f);
glEnd();
// Triangles For Y axis
glBegin(GL_TRIANGLES);
glColor3f(0.f, 1.f, 0.f);
glVertex3f(0.f, axis_size * 1.1f, 0.0f);
glVertex3f(0.f, axis_size, 0.05f * axis_size);
glVertex3f(0.f, axis_size, -0.05f * axis_size);
glVertex3f(0.f, axis_size * 1.1f, 0.0f);
glVertex3f(0.05f * axis_size, axis_size, 0.f);
glVertex3f(-0.05f * axis_size, axis_size, 0.f);
glEnd();
// Triangles For Z axis
glBegin(GL_TRIANGLES);
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 1.1f * axis_size);
glVertex3f(0.0f, 0.05f * axis_size, 1.0f * axis_size);
glVertex3f(0.0f, -0.05f * axis_size, 1.0f * axis_size);
glVertex3f(0.0f, 0.0f, 1.1f * axis_size);
glVertex3f(0.05f * axis_size, 0.f, 1.0f * axis_size);
glVertex3f(-0.05f * axis_size, 0.f, 1.0f * axis_size);
glEnd();
glLineWidth(axisWidth);
// Drawing Axis
glBegin(GL_LINES);
// X axis - Red
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(axis_size, 0.0f, 0.0f);
// Y axis - Green
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, axis_size, 0.0f);
// Z axis - Blue
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, axis_size);
glEnd();
}
// intensity is grey intensity
static void draw_circle(float xx, float xy, float xz, float yx, float yy, float yz, float radius = 1.1, float3 center = { 0.0, 0.0, 0.0 }, float intensity = 0.5f)
{
const auto N = 50;
glColor3f(intensity, intensity, intensity);
glLineWidth(2);
glBegin(GL_LINE_STRIP);
for (int i = 0; i <= N; i++)
{
const double theta = (2 * PI / N) * i;
const auto cost = static_cast<float>(cos(theta));
const auto sint = static_cast<float>(sin(theta));
glVertex3f(
center.x + radius * (xx * cost + yx * sint),
center.y + radius * (xy * cost + yy * sint),
center.z + radius * (xz * cost + yz * sint)
);
}
glEnd();
}
};
class pose_renderer
{
public:
void render(const rs2::pose_frame& frame, const rect& r)
{
draw_pose(frame, r.adjust_ratio({ IMU_FRAME_WIDTH, IMU_FRAME_HEIGHT }));
}
GLuint get_gl_handle() { return _gl_handle; }
private:
mutable GLuint _gl_handle = 0;
// Provide textual representation only
void draw_pose(const rs2::pose_frame& f, const rect& r)
{
if (!_gl_handle)
glGenTextures(1, &_gl_handle);
set_viewport(r);
std::string caption(f.get_profile().stream_name());
if (f.get_profile().stream_index())
caption += std::to_string(f.get_profile().stream_index());
draw_text(int(0.05f * r.w), int(0.05f * r.h), caption.c_str());
auto pose = f.get_pose_data();
std::stringstream ss;
ss << "Pos (meter): \t\t" << std::fixed << std::setprecision(2) << pose.translation.x << ", " << pose.translation.y << ", " << pose.translation.z;
draw_text(int(0.05f * r.w), int(0.2f * r.h), ss.str().c_str());
ss.clear(); ss.str("");
ss << "Orient (quaternion): \t" << pose.rotation.x << ", " << pose.rotation.y << ", " << pose.rotation.z << ", " << pose.rotation.w;
draw_text(int(0.05f * r.w), int(0.3f * r.h), ss.str().c_str());
ss.clear(); ss.str("");
ss << "Lin Velocity (m/sec): \t" << pose.velocity.x << ", " << pose.velocity.y << ", " << pose.velocity.z;
draw_text(int(0.05f * r.w), int(0.4f * r.h), ss.str().c_str());
ss.clear(); ss.str("");
ss << "Ang. Velocity (rad/sec): \t" << pose.angular_velocity.x << ", " << pose.angular_velocity.y << ", " << pose.angular_velocity.z;
draw_text(int(0.05f * r.w), int(0.5f * r.h), ss.str().c_str());
}
};
/// \brief Print flat 2D text over openGl window
struct text_renderer
{
// Provide textual representation only
void put_text(const std::string& msg, float norm_x_pos, float norm_y_pos, const rect& r)
{
set_viewport(r);
draw_text(int(norm_x_pos * r.w), int(norm_y_pos * r.h), msg.c_str());
}
};
////////////////////////
// Image display code //
////////////////////////
/// \brief The texture class
class texture
{
public:
void upload(const rs2::video_frame& frame)
{
if (!frame) return;
if (!_gl_handle)
glGenTextures(1, &_gl_handle);
GLenum err = glGetError();
auto format = frame.get_profile().format();
auto width = frame.get_width();
auto height = frame.get_height();
_stream_type = frame.get_profile().stream_type();
_stream_index = frame.get_profile().stream_index();
glBindTexture(GL_TEXTURE_2D, _gl_handle);
switch (format)
{
case RS2_FORMAT_RGB8:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, frame.get_data());
break;
case RS2_FORMAT_RGBA8:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame.get_data());
break;
case RS2_FORMAT_Y8:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame.get_data());
break;
case RS2_FORMAT_Y10BPACK:
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_SHORT, frame.get_data());
break;
default:
throw std::runtime_error("The requested format is not supported by this demo!");
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
void show(const rect& r, float alpha = 1.f) const
{
if (!_gl_handle)
return;
set_viewport(r);
glBindTexture(GL_TEXTURE_2D, _gl_handle);
glColor4f(1.0f, 1.0f, 1.0f, alpha);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex2f(0, 0);
glTexCoord2f(0, 1); glVertex2f(0, r.h);
glTexCoord2f(1, 1); glVertex2f(r.w, r.h);
glTexCoord2f(1, 0); glVertex2f(r.w, 0);
glEnd();
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
draw_text(int(0.05f * r.w), int(0.05f * r.h), rs2_stream_to_string(_stream_type));
}
GLuint get_gl_handle() { return _gl_handle; }
void render(const rs2::frame& frame, const rect& rect, float alpha = 1.f)
{
if (auto vf = frame.as<rs2::video_frame>())
{
upload(vf);
show(rect.adjust_ratio({ (float)vf.get_width(), (float)vf.get_height() }), alpha);
}
else if (auto mf = frame.as<rs2::motion_frame>())
{
_imu_render.render(frame, rect.adjust_ratio({ IMU_FRAME_WIDTH, IMU_FRAME_HEIGHT }));
}
else if (auto pf = frame.as<rs2::pose_frame>())
{
_pose_render.render(frame, rect.adjust_ratio({ IMU_FRAME_WIDTH, IMU_FRAME_HEIGHT }));
}
else
throw std::runtime_error("Rendering is currently supported for video, motion and pose frames only");
}
private:
GLuint _gl_handle = 0;
rs2_stream _stream_type = RS2_STREAM_ANY;
int _stream_index{};
imu_renderer _imu_render;
pose_renderer _pose_render;
};
class window
{
public:
std::function<void(bool)> on_left_mouse = [](bool) {};
std::function<void(double, double)> on_mouse_scroll = [](double, double) {};
std::function<void(double, double)> on_mouse_move = [](double, double) {};
std::function<void(int)> on_key_release = [](int) {};
window(int width, int height, const char* title)
: _width(width), _height(height), _canvas_left_top_x(0), _canvas_left_top_y(0), _canvas_width(width), _canvas_height(height)
{
glfwInit();
win = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (!win)
throw std::runtime_error("Could not open OpenGL window, please check your graphic drivers or use the textual SDK tools");
glfwMakeContextCurrent(win);
glfwSetWindowUserPointer(win, this);
glfwSetMouseButtonCallback(win, [](GLFWwindow* w, int button, int action, int mods)
{
auto s = (window*)glfwGetWindowUserPointer(w);
if (button == 0) s->on_left_mouse(action == GLFW_PRESS);
});
glfwSetScrollCallback(win, [](GLFWwindow* w, double xoffset, double yoffset)
{
auto s = (window*)glfwGetWindowUserPointer(w);
s->on_mouse_scroll(xoffset, yoffset);
});
glfwSetCursorPosCallback(win, [](GLFWwindow* w, double x, double y)
{
auto s = (window*)glfwGetWindowUserPointer(w);
s->on_mouse_move(x, y);
});
glfwSetKeyCallback(win, [](GLFWwindow* w, int key, int scancode, int action, int mods)
{
auto s = (window*)glfwGetWindowUserPointer(w);
if (0 == action) // on key release
{
s->on_key_release(key);
}
});
}
//another c'tor for adjusting specific frames in specific tiles, this window is NOT resizeable
window(unsigned width, unsigned height, const char* title, unsigned tiles_in_row, unsigned tiles_in_col, float canvas_width = 0.8f,
float canvas_height = 0.6f, float canvas_left_top_x = 0.1f, float canvas_left_top_y = 0.075f)
: _width(width), _height(height), _tiles_in_row(tiles_in_row), _tiles_in_col(tiles_in_col)
{
//user input verification for mosaic size, if invalid values were given - set to default
if (canvas_width < 0 || canvas_width > 1 || canvas_height < 0 || canvas_height > 1 ||
canvas_left_top_x < 0 || canvas_left_top_x > 1 || canvas_left_top_y < 0 || canvas_left_top_y > 1)
{
std::cout << "Invalid window's size parameter entered, setting to default values" << std::endl;
canvas_width = 0.8f;
canvas_height = 0.6f;
canvas_left_top_x = 0.15f;
canvas_left_top_y = 0.075f;
}
//user input verification for number of tiles in row and column
if (_tiles_in_row <= 0) {
_tiles_in_row = 4;
}
if (_tiles_in_col <= 0) {
_tiles_in_col = 2;
}
//calculate canvas size
_canvas_width = int(_width * canvas_width);
_canvas_height = int(_height * canvas_height);
_canvas_left_top_x = _width * canvas_left_top_x;
_canvas_left_top_y = _height * canvas_left_top_y;
//calculate tile size
_tile_width_pixels = float(std::floor(_canvas_width / _tiles_in_row));
_tile_height_pixels = float(std::floor(_canvas_height / _tiles_in_col));
glfwInit();
// we don't want to enable resizing the window
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
win = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (!win)
throw std::runtime_error("Could not open OpenGL window, please check your graphic drivers or use the textual SDK tools");
glfwMakeContextCurrent(win);
glfwSetWindowUserPointer(win, this);
glfwSetMouseButtonCallback(win, [](GLFWwindow* w, int button, int action, int mods)
{
auto s = (window*)glfwGetWindowUserPointer(w);
if (button == 0) s->on_left_mouse(action == GLFW_PRESS);
});
glfwSetScrollCallback(win, [](GLFWwindow* w, double xoffset, double yoffset)
{
auto s = (window*)glfwGetWindowUserPointer(w);
s->on_mouse_scroll(xoffset, yoffset);
});
glfwSetCursorPosCallback(win, [](GLFWwindow* w, double x, double y)
{
auto s = (window*)glfwGetWindowUserPointer(w);
s->on_mouse_move(x, y);
});
glfwSetKeyCallback(win, [](GLFWwindow* w, int key, int scancode, int action, int mods)
{
auto s = (window*)glfwGetWindowUserPointer(w);
if (0 == action) // on key release
{
s->on_key_release(key);
}
});
}
~window()
{
glfwDestroyWindow(win);
glfwTerminate();
}
void close()
{
glfwSetWindowShouldClose(win, 1);
}
float width() const { return float(_width); }
float height() const { return float(_height); }
operator bool()
{
glPopMatrix();
glfwSwapBuffers(win);
auto res = !glfwWindowShouldClose(win);
glfwPollEvents();
glfwGetFramebufferSize(win, &_width, &_height);
// Clear the framebuffer
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, _width, _height);
// Draw the images
glPushMatrix();
glfwGetWindowSize(win, &_width, &_height);
glOrtho(0, _width, _height, 0, -1, +1);
return res;
}
void show(rs2::frame frame)
{
show(frame, { 0, 0, (float)_width, (float)_height });
}
void show(const rs2::frame& frame, const rect& rect)
{
if (auto fs = frame.as<rs2::frameset>())
render_frameset(fs, rect);
else if (auto vf = frame.as<rs2::depth_frame>())
render_video_frame(vf.apply_filter(_colorizer), rect);
else if (auto vf = frame.as<rs2::video_frame>())
render_video_frame(vf, rect);
else if (auto mf = frame.as<rs2::motion_frame>())
render_motion_frame(mf, rect);
else if (auto pf = frame.as<rs2::pose_frame>())
render_pose_frame(pf, rect);
}
void show(const std::map<int, rs2::frame>& frames)
{
// Render openGl mosaic of frames
if (frames.size())
{
int cols = int(std::ceil(std::sqrt(frames.size())));
int rows = int(std::ceil(frames.size() / static_cast<float>(cols)));
float view_width = float(_width / cols);
float view_height = float(_height / rows);
unsigned int stream_no = 0;
for (auto& frame : frames)
{
rect viewport_loc{ view_width * (stream_no % cols), view_height * (stream_no / cols), view_width, view_height };
show(frame.second, viewport_loc);
stream_no++;
}
}
else
{
_main_win.put_text("Connect one or more Intel RealSense devices and rerun the example",
0.4f, 0.5f, { 0.f,0.f, float(_width) , float(_height) });
}
}
//gets as argument a map of the -need to be drawn- frames with their tiles properties,
//which indicates where and what size should the frame be drawn on the canvas
void show(const frames_mosaic& frames)
{
// Render openGl mosaic of frames
if (frames.size())
{
// create vector of frames from map, and sort it by priority
std::vector <frame_and_tile_property> vector_frames;
//copy: map (values) -> vector
for (const auto& frame : frames) { vector_frames.push_back(frame.second); }
//sort in ascending order of the priority
std::sort(vector_frames.begin(), vector_frames.end(),
[](const frame_and_tile_property& frame1, const frame_and_tile_property& frame2)
{
return frame1.second.priority < frame2.second.priority;
});
//create margin to the shown frame on tile
float frame_width_size_from_tile_width = 1.0f;
//iterate over frames in ascending priority order (so that lower priority frame is drawn first, and can be over-written by higher priority frame )
for (const auto& frame : vector_frames)
{
tile_properties attr = frame.second;
rect viewport_loc{ _tile_width_pixels * attr.x + _canvas_left_top_x, _tile_height_pixels * attr.y + _canvas_left_top_y,
_tile_width_pixels * attr.w * frame_width_size_from_tile_width, _tile_height_pixels * attr.h };
show(frame.first, viewport_loc);
}
}
else
{
_main_win.put_text("Connect one or more Intel RealSense devices and rerun the example",
0.3f, 0.5f, { float(_canvas_left_top_x), float(_canvas_left_top_y), float(_canvas_width) , float(_canvas_height) });
}
}
/////////////////////////////////////////////////////////////
// get_pos_on_current_image:
// There may be several windows displayed on the sceen, as described in the frames_mosaic structure.
// The windows are displayed in a reduced resolution, appropriate the amount of space allocated for them on the screen.
// This function converts from screen pixel to original image pixel.
//
// Input:
// pos - pixel in screen coordinates.
// frames - structure of separate windows displayed on screen.
// Returns:
// The index of the window the screen pixel is in and the pixel in that window in the original window's resolution.
frame_pixel get_pos_on_current_image(float2 pos, const frames_mosaic& frames)
{
frame_pixel res{ -1, -1,-1 };
for (auto& frame : frames)
{
if (auto vf = frame.second.first.as<rs2::video_frame>())
{
tile_properties attr = frame.second.second;
float frame_width_size_from_tile_width = 1.0f;
rect viewport_loc{ _tile_width_pixels * attr.x + _canvas_left_top_x, _tile_height_pixels * attr.y + _canvas_left_top_y,
_tile_width_pixels * attr.w * frame_width_size_from_tile_width, _tile_height_pixels * attr.h };
viewport_loc = viewport_loc.adjust_ratio({ (float)vf.get_width(), (float)vf.get_height() });
if (pos.x >= viewport_loc.x && pos.x < viewport_loc.x + viewport_loc.w &&
pos.y >= _height - (viewport_loc.y + viewport_loc.h) && pos.y < _height - viewport_loc.y)
{
float image_rect_ratio = (float)vf.get_width() / viewport_loc.w; //Ratio for y-axis is the same.
res.frame_idx = frame.first;
res.pixel.x = (pos.x - viewport_loc.x) * image_rect_ratio;
res.pixel.y = (pos.y - (_height - (viewport_loc.y + viewport_loc.h))) * image_rect_ratio;
break;
}
}
}
return res;
}
operator GLFWwindow* () { return win; }
private:
GLFWwindow* win;
std::map<int, texture> _textures;
std::map<int, imu_renderer> _imus;
std::map<int, pose_renderer> _poses;
text_renderer _main_win;
int _width, _height;
float _canvas_left_top_x, _canvas_left_top_y;
int _canvas_width, _canvas_height;
unsigned _tiles_in_row, _tiles_in_col;
float _tile_width_pixels, _tile_height_pixels;
rs2::colorizer _colorizer;
void render_video_frame(const rs2::video_frame& f, const rect& r)
{
auto& t = _textures[f.get_profile().unique_id()];
t.render(f, r);
}
void render_motion_frame(const rs2::motion_frame& f, const rect& r)
{
auto& i = _imus[f.get_profile().unique_id()];
i.render(f, r);
}
void render_pose_frame(const rs2::pose_frame& f, const rect& r)
{
auto& i = _poses[f.get_profile().unique_id()];
i.render(f, r);
}
void render_frameset(const rs2::frameset& frames, const rect& r)
{
std::vector<rs2::frame> supported_frames;
for (auto f : frames)
{
if (can_render(f))
supported_frames.push_back(f);
}
if (supported_frames.empty())
return;
std::sort(supported_frames.begin(), supported_frames.end(), [](rs2::frame first, rs2::frame second)
{ return first.get_profile().stream_type() < second.get_profile().stream_type(); });
auto image_grid = calc_grid(r, supported_frames);
int image_index = 0;
for (auto f : supported_frames)
{
auto r = image_grid.at(image_index);
show(f, r);
image_index++;
}
}
bool can_render(const rs2::frame& f) const
{
auto format = f.get_profile().format();
switch (format)
{
case RS2_FORMAT_RGB8:
case RS2_FORMAT_RGBA8:
case RS2_FORMAT_Y8:
case RS2_FORMAT_MOTION_XYZ32F:
case RS2_FORMAT_Y10BPACK:
return true;
default:
return false;
}
}
rect calc_grid(rect r, size_t streams)
{
if (r.w <= 0 || r.h <= 0 || streams <= 0)
throw std::runtime_error("invalid window configuration request, failed to calculate window grid");
float ratio = r.w / r.h;
auto x = sqrt(ratio * (float)streams);
auto y = (float)streams / x;
auto w = round(x);
auto h = round(y);
if (w == 0 || h == 0)
throw std::runtime_error("invalid window configuration request, failed to calculate window grid");
while (w * h > streams)
h > w ? h-- : w--;
while (w* h < streams)
h > w ? w++ : h++;
auto new_w = round(r.w / w);
auto new_h = round(r.h / h);
// column count, line count, cell width cell height
return rect{ static_cast<float>(w), static_cast<float>(h), static_cast<float>(new_w), static_cast<float>(new_h) };
}
std::vector<rect> calc_grid(rect r, std::vector<rs2::frame>& frames)
{
auto grid = calc_grid(r, frames.size());
std::vector<rect> rv;
int curr_line = -1;
for (int i = 0; i < frames.size(); i++)
{
auto mod = i % (int)grid.x;
float fw = IMU_FRAME_WIDTH;
float fh = IMU_FRAME_HEIGHT;
if (auto vf = frames[i].as<rs2::video_frame>())
{
fw = (float)vf.get_width();
fh = (float)vf.get_height();
}
float cell_x_postion = (float)(mod * grid.w);
if (mod == 0) curr_line++;
float cell_y_position = curr_line * grid.h;
float2 margin = { grid.w * 0.02f, grid.h * 0.02f };
auto r = rect{ cell_x_postion + margin.x, cell_y_position + margin.y, grid.w - 2 * margin.x, grid.h };
rv.push_back(r.adjust_ratio(float2{ fw, fh }));
}
return rv;
}
};
// Struct to get keys pressed on window
struct window_key_listener {
int last_key = GLFW_KEY_UNKNOWN;
window_key_listener(window& win) {
win.on_key_release = std::bind(&window_key_listener::on_key_release, this, std::placeholders::_1);
}
void on_key_release(int key) {
last_key = key;
}
int get_key() {
int key = last_key;
last_key = GLFW_KEY_UNKNOWN;
return key;
}
};
// Struct for managing rotation of pointcloud view
struct glfw_state {
glfw_state(float yaw = 15.0, float pitch = 15.0) : yaw(yaw), pitch(pitch), last_x(0.0), last_y(0.0),
ml(false), offset_x(2.f), offset_y(2.f), tex() {}
double yaw;
double pitch;
double last_x;
double last_y;
bool ml;
float offset_x;
float offset_y;
texture tex;
};
// Handles all the OpenGL calls needed to display the point cloud
void draw_pointcloud(float width, float height, glfw_state& app_state, rs2::points& points)
{
if (!points)
return;
// OpenGL commands that prep screen for the pointcloud
glLoadIdentity();
glPushAttrib(GL_ALL_ATTRIB_BITS);
glClearColor(153.f / 255, 153.f / 255, 153.f / 255, 1);
glClear(GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
gluPerspective(60, width / height, 0.01f, 10.0f);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
gluLookAt(0, 0, 0, 0, 0, 1, 0, -1, 0);
glTranslatef(0, 0, +0.5f + app_state.offset_y * 0.05f);
glRotated(app_state.pitch, 1, 0, 0);
glRotated(app_state.yaw, 0, 1, 0);
glTranslatef(0, 0, -0.5f);
glPointSize(width / 640);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, app_state.tex.get_gl_handle());
float tex_border_color[] = { 0.8f, 0.8f, 0.8f, 0.8f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, tex_border_color);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0x812F); // GL_CLAMP_TO_EDGE
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 0x812F); // GL_CLAMP_TO_EDGE
glBegin(GL_POINTS);
/* this segment actually prints the pointcloud */
auto vertices = points.get_vertices(); // get vertices
auto tex_coords = points.get_texture_coordinates(); // and texture coordinates
for (int i = 0; i < points.size(); i++)
{
if (vertices[i].z)
{
// upload the point and texture coordinates only for points we have depth data for
glVertex3fv(vertices[i]);
glTexCoord2fv(tex_coords[i]);
}
}
// OpenGL cleanup
glEnd();
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
}
void quat2mat(rs2_quaternion& q, GLfloat H[16]) // to column-major matrix
{
H[0] = 1 - 2 * q.y * q.y - 2 * q.z * q.z; H[4] = 2 * q.x * q.y - 2 * q.z * q.w; H[8] = 2 * q.x * q.z + 2 * q.y * q.w; H[12] = 0.0f;
H[1] = 2 * q.x * q.y + 2 * q.z * q.w; H[5] = 1 - 2 * q.x * q.x - 2 * q.z * q.z; H[9] = 2 * q.y * q.z - 2 * q.x * q.w; H[13] = 0.0f;
H[2] = 2 * q.x * q.z - 2 * q.y * q.w; H[6] = 2 * q.y * q.z + 2 * q.x * q.w; H[10] = 1 - 2 * q.x * q.x - 2 * q.y * q.y; H[14] = 0.0f;
H[3] = 0.0f; H[7] = 0.0f; H[11] = 0.0f; H[15] = 1.0f;
}
// Handles all the OpenGL calls needed to display the point cloud w.r.t. static reference frame
void draw_pointcloud_wrt_world(float width, float height, glfw_state& app_state, rs2::points& points, rs2_pose& pose, float H_t265_d400[16], std::vector<rs2_vector>& trajectory)
{
if (!points)
return;
// OpenGL commands that prep screen for the pointcloud
glLoadIdentity();
glPushAttrib(GL_ALL_ATTRIB_BITS);
glClearColor(153.f / 255, 153.f / 255, 153.f / 255, 1);
glClear(GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
gluPerspective(60, width / height, 0.01f, 10.0f);
// viewing matrix
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
// rotated from depth to world frame: z => -z, y => -y
glTranslatef(0, 0, -0.75f - app_state.offset_y * 0.05f);
glRotated(app_state.pitch, 1, 0, 0);
glRotated(app_state.yaw, 0, -1, 0);
glTranslatef(0, 0, 0.5f);
// draw trajectory
glEnable(GL_DEPTH_TEST);
glLineWidth(2.0f);
glBegin(GL_LINE_STRIP);
for (auto&& v : trajectory)
{
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(v.x, v.y, v.z);
}
glEnd();
glLineWidth(0.5f);
glColor3f(1.0f, 1.0f, 1.0f);
// T265 pose
GLfloat H_world_t265[16];
quat2mat(pose.rotation, H_world_t265);
H_world_t265[12] = pose.translation.x;
H_world_t265[13] = pose.translation.y;
H_world_t265[14] = pose.translation.z;
glMultMatrixf(H_world_t265);
// T265 to D4xx extrinsics
glMultMatrixf(H_t265_d400);
glPointSize(width / 640);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, app_state.tex.get_gl_handle());
float tex_border_color[] = { 0.8f, 0.8f, 0.8f, 0.8f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, tex_border_color);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 0x812F); // GL_CLAMP_TO_EDGE
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 0x812F); // GL_CLAMP_TO_EDGE
glBegin(GL_POINTS);
/* this segment actually prints the pointcloud */
auto vertices = points.get_vertices(); // get vertices
auto tex_coords = points.get_texture_coordinates(); // and texture coordinates
for (int i = 0; i < points.size(); i++)
{
if (vertices[i].z)
{
// upload the point and texture coordinates only for points we have depth data for
glVertex3fv(vertices[i]);
glTexCoord2fv(tex_coords[i]);
}
}
// OpenGL cleanup
glEnd();
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
}
// Registers the state variable and callbacks to allow mouse control of the pointcloud
void register_glfw_callbacks(window& app, glfw_state& app_state)
{
app.on_left_mouse = [&](bool pressed)
{
app_state.ml = pressed;
};
app.on_mouse_scroll = [&](double xoffset, double yoffset)
{
app_state.offset_x -= static_cast<float>(xoffset);
app_state.offset_y -= static_cast<float>(yoffset);
};
app.on_mouse_move = [&](double x, double y)
{
if (app_state.ml)
{
app_state.yaw -= (x - app_state.last_x);
app_state.yaw = std::max(app_state.yaw, -120.0);
app_state.yaw = std::min(app_state.yaw, +120.0);
app_state.pitch += (y - app_state.last_y);
app_state.pitch = std::max(app_state.pitch, -80.0);
app_state.pitch = std::min(app_state.pitch, +80.0);
}
app_state.last_x = x;
app_state.last_y = y;
};
app.on_key_release = [&](int key)
{
if (key == 32) // Escape
{
app_state.yaw = app_state.pitch = 0; app_state.offset_x = app_state.offset_y = 0.0;
}
};
}
void get_screen_resolution(unsigned int& window_width, unsigned int& window_height) {
glfwInit();
const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
window_width = mode->width;
window_height = mode->height;
}