mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 01:18:34 +00:00
511 lines
17 KiB
Rust
511 lines
17 KiB
Rust
//! Based off Microsoft's MIT-licensed k4a library code and examples, specifically
|
|
//! `k4apointcloudvisualizer.h`
|
|
|
|
use std::fmt::Formatter;
|
|
use std::mem::size_of;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use gl::types::*;
|
|
|
|
use core_types::RgbaF32;
|
|
use gui::mouse_camera_arcball::MouseCameraArcball;
|
|
use kinect::k4a_sys_wrapper::Image;
|
|
use kinect::k4a_sys_wrapper::ImageFormat;
|
|
use kinect::k4a_sys_wrapper::{Capture, Transformation};
|
|
use opengl::opengl_wrapper::OpenGlError;
|
|
use opengl::opengl_wrapper::{Framebuffer, Renderbuffer, Texture};
|
|
use point_cloud::gpu_point_cloud_converter::{GpuPointCloudConverter, PointCloudComputeError};
|
|
use point_cloud::pixel_structs::BgraPixel;
|
|
use point_cloud::pixel_structs::DepthPixel;
|
|
use point_cloud::point_cloud_renderer::{PointCloudRenderer, PointCloudRendererError};
|
|
use point_cloud::util::{colorize_depth_blue_to_red, get_depth_mode_range, ValueRange};
|
|
use point_cloud::viewer_image::ViewerImage;
|
|
|
|
pub type Result<T> = std::result::Result<T, PointCloudVisualizerError>;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum PointCloudVisualizerError {
|
|
OpenGlError(OpenGlError),
|
|
PointCloudRendererError(PointCloudRendererError),
|
|
PointCloudComputeError(PointCloudComputeError),
|
|
FramebufferError,
|
|
DepthToColorConversionFailed,
|
|
/// Capture is missing depth info; drop the frame
|
|
MissingDepthImage,
|
|
/// Capture is missing color info; drop the frame
|
|
MissingColorImage,
|
|
// TODO: Implement this
|
|
ColorSupportNotYetImplemented,
|
|
UnsupportedMode,
|
|
UnknownError,
|
|
}
|
|
|
|
impl From<OpenGlError> for PointCloudVisualizerError {
|
|
fn from(error: OpenGlError) -> Self {
|
|
PointCloudVisualizerError::OpenGlError(error)
|
|
}
|
|
}
|
|
|
|
impl From<PointCloudComputeError> for PointCloudVisualizerError {
|
|
fn from(error: PointCloudComputeError) -> Self {
|
|
PointCloudVisualizerError::PointCloudComputeError(error)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for PointCloudVisualizerError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
let description = match self {
|
|
PointCloudVisualizerError::OpenGlError(inner) => {
|
|
format!("Visualizer OpenGL error: {}", inner)
|
|
},
|
|
PointCloudVisualizerError::PointCloudRendererError(inner) => {
|
|
format!("Visualizer PointCloudRenderer error: {}", inner)
|
|
},
|
|
PointCloudVisualizerError::PointCloudComputeError(inner) => {
|
|
format!("Visualizer PointCloudCompute error: {}", inner)
|
|
},
|
|
PointCloudVisualizerError::FramebufferError => "Visualizer Framebuffer Error".into(),
|
|
PointCloudVisualizerError::DepthToColorConversionFailed => "Visualizer Depth To Color Conversion Error".into(),
|
|
PointCloudVisualizerError::UnsupportedMode => "Visualizer Unsupported Mode Error".into(),
|
|
PointCloudVisualizerError::MissingDepthImage => "Visualizer Missing Depth Image (drop frame)".into(),
|
|
PointCloudVisualizerError::MissingColorImage => "Visualizer Missing Color Image (drop frame)".into(),
|
|
PointCloudVisualizerError::ColorSupportNotYetImplemented => "Visualizer Color Support Not Yet Implemented (TODO)".into(),
|
|
PointCloudVisualizerError::UnknownError => "Visualizer Unknown Error".into(),
|
|
};
|
|
|
|
write!(f, "{}", description)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for PointCloudVisualizerError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
|
|
// Generic error, no backtrace.
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub enum ColorizationStrategy {
|
|
Simple,
|
|
Shaded,
|
|
Color,
|
|
}
|
|
|
|
pub struct PointCloudVisualizer {
|
|
arcball_camera: Arc<Mutex<MouseCameraArcball>>,
|
|
|
|
num_cameras: usize,
|
|
|
|
m_dimensions_width : i32,
|
|
m_dimensions_height : i32,
|
|
|
|
enable_color_point_cloud: bool,
|
|
colorization_strategy: ColorizationStrategy,
|
|
|
|
pub point_cloud_renderer: PointCloudRenderer,
|
|
pub point_cloud_converters: Vec<GpuPointCloudConverter>,
|
|
|
|
calibration_data: k4a_sys::k4a_calibration_t,
|
|
transformation: Transformation, // TODO: WAT k4a_sys::k4a_transformation_t
|
|
|
|
frame_buffer: Framebuffer,
|
|
depth_buffer: Renderbuffer,
|
|
|
|
last_captures: Vec<Option<Capture>>,
|
|
|
|
/// Buffer that holds the depth image transformed to the color coordinate space.
|
|
/// Used in color mode only.
|
|
transformed_depth_images: Vec<Option<Image>>,
|
|
|
|
/// In color mode, this is just a shallow copy of the latest color image.
|
|
/// In depth mode, this is a buffer that holds the colorization of the depth image.
|
|
point_cloud_colorizations: Vec<Option<Image>>,
|
|
|
|
/// Holds the XYZ point cloud as a texture.
|
|
/// Format is XYZA, where A (the alpha channel) is unused.
|
|
pub xyz_textures: Vec<Texture>,
|
|
|
|
color_xy_tables: Vec<Image>,
|
|
depth_xy_tables: Vec<Image>,
|
|
|
|
/// Near and far minima/maxima for the current depth sensor mode.
|
|
depth_value_range: ValueRange,
|
|
|
|
/// Background clear color for OpenGL.
|
|
clear_color: RgbaF32,
|
|
}
|
|
|
|
// TODO: Dedup
|
|
struct CleanupGuard {}
|
|
|
|
impl Drop for CleanupGuard {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PointCloudVisualizer {
|
|
|
|
///
|
|
/// CTOR
|
|
///
|
|
pub fn new(num_cameras: usize,
|
|
enable_color_point_cloud: bool,
|
|
initial_colorization_strategy: ColorizationStrategy,
|
|
calibration_data: k4a_sys::k4a_calibration_t,
|
|
clear_color: RgbaF32,
|
|
arcball_camera: Arc<Mutex<MouseCameraArcball>>) -> Self
|
|
{
|
|
// Resolution of the point cloud texture
|
|
// constexpr ImageDimensions PointCloudVisualizerTextureDimensions = { 1280, 1152 };
|
|
let width = 1280;
|
|
let height= 1152;
|
|
|
|
let depth_buffer = Renderbuffer::new_initialized();
|
|
|
|
unsafe {
|
|
gl::BindRenderbuffer(gl::RENDERBUFFER, depth_buffer.id());
|
|
gl::RenderbufferStorage(gl::RENDERBUFFER, gl::DEPTH_COMPONENT, width, height);
|
|
}
|
|
|
|
let expected_value_range = get_depth_mode_range(calibration_data.depth_mode)
|
|
.expect("Should be in correct depth sensor mode.");
|
|
|
|
let mut point_cloud_converters = Vec::with_capacity(num_cameras);
|
|
let mut transformed_depth_images = Vec::with_capacity(num_cameras);
|
|
let mut point_cloud_colorizations = Vec::with_capacity(num_cameras);
|
|
let mut xyz_textures = Vec::with_capacity(num_cameras);
|
|
let mut last_captures = Vec::with_capacity(num_cameras);
|
|
let mut color_xy_tables = Vec::with_capacity(num_cameras);
|
|
let mut depth_xy_tables = Vec::with_capacity(num_cameras);
|
|
|
|
for _ in 0 .. num_cameras {
|
|
point_cloud_converters.push(GpuPointCloudConverter::new());
|
|
transformed_depth_images.push(None);
|
|
point_cloud_colorizations.push(None);
|
|
last_captures.push(None);
|
|
xyz_textures.push(Texture::new());
|
|
|
|
// TODO: generate color xytable only on `enable_color_point_cloud`.
|
|
let color_xy_table = GpuPointCloudConverter::generate_xy_table(
|
|
calibration_data.clone(),
|
|
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_COLOR,
|
|
).unwrap();
|
|
|
|
let depth_xy_table = GpuPointCloudConverter::generate_xy_table(
|
|
calibration_data.clone(),
|
|
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH,
|
|
).unwrap();
|
|
|
|
color_xy_tables.push(color_xy_table);
|
|
depth_xy_tables.push(depth_xy_table);
|
|
}
|
|
|
|
let mut visualizer = Self {
|
|
num_cameras,
|
|
arcball_camera: arcball_camera.clone(),
|
|
m_dimensions_width: width,
|
|
m_dimensions_height: height,
|
|
enable_color_point_cloud,
|
|
point_cloud_renderer: PointCloudRenderer::new(num_cameras, arcball_camera.clone()),
|
|
point_cloud_converters,
|
|
frame_buffer: Framebuffer::new_initialized(),
|
|
depth_buffer,
|
|
color_xy_tables,
|
|
depth_xy_tables,
|
|
colorization_strategy: initial_colorization_strategy,
|
|
calibration_data: calibration_data.clone(),
|
|
transformation: Transformation::from_calibration(&calibration_data),
|
|
last_captures,
|
|
transformed_depth_images,
|
|
point_cloud_colorizations,
|
|
xyz_textures,
|
|
depth_value_range: expected_value_range,
|
|
clear_color,
|
|
};
|
|
|
|
visualizer.set_colorization_strategy(initial_colorization_strategy).expect("Should work");
|
|
|
|
visualizer
|
|
}
|
|
|
|
///
|
|
///
|
|
///
|
|
pub fn set_point_size(&mut self, point_size: u8) {
|
|
self.point_cloud_renderer.set_point_size(point_size);
|
|
}
|
|
|
|
///
|
|
///
|
|
///
|
|
pub fn update_texture(&mut self, texture: &ViewerImage, mut captures: Vec<Capture>) -> Result<()> {
|
|
self.update_texture_id(texture.texture_id(), captures)
|
|
}
|
|
|
|
///
|
|
/// Take the latest capture, calculate updates to the xyz texture, then render the point cloud to
|
|
/// the output texture.
|
|
///
|
|
pub fn update_texture_id(&mut self, texture_id: GLuint, mut captures: Vec<Capture>) -> Result<()> {
|
|
// Update the point cloud renderer with the latest point data
|
|
self.update_point_clouds(captures)?;
|
|
|
|
// Set up rendering to a texture
|
|
unsafe {
|
|
gl::BindRenderbuffer(gl::RENDERBUFFER, self.depth_buffer.id());
|
|
gl::BindFramebuffer(gl::FRAMEBUFFER, self.frame_buffer.id());
|
|
}
|
|
|
|
let _cleanup_guard = CleanupGuard {};
|
|
|
|
unsafe {
|
|
gl::FramebufferRenderbuffer(
|
|
gl::FRAMEBUFFER,
|
|
gl::DEPTH_ATTACHMENT,
|
|
gl::RENDERBUFFER,
|
|
self.depth_buffer.id()
|
|
);
|
|
|
|
gl::FramebufferTexture(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, texture_id, 0);
|
|
|
|
// DrawBuffers writes to a COLOR BUFFER
|
|
// COLOR_ATTACHMENT{N} - The fragment shader output value is written into the nth color
|
|
// attachment of the current framebuffer.
|
|
gl::DrawBuffers(1, &gl::COLOR_ATTACHMENT0);
|
|
|
|
let frame_buffer_status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
|
|
|
|
if frame_buffer_status != gl::FRAMEBUFFER_COMPLETE {
|
|
return Err(PointCloudVisualizerError::FramebufferError);
|
|
}
|
|
|
|
gl::Viewport(0, 0, self.m_dimensions_width, self.m_dimensions_height);
|
|
gl::Enable(gl::DEPTH_TEST);
|
|
gl::ClearColor(
|
|
self.clear_color.red,
|
|
self.clear_color.green,
|
|
self.clear_color.blue,
|
|
self.clear_color.alpha,
|
|
);
|
|
gl::ClearDepth(1.0);
|
|
|
|
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
|
}
|
|
|
|
match self.arcball_camera.lock() {
|
|
Ok(arcball) => {
|
|
let view: [[f32; 4]; 4] = (arcball.get_view_matrix()).into();
|
|
let perspective: [[f32; 4]; 4] = (arcball.get_perspective_matrix()).into();
|
|
self.point_cloud_renderer.update_view_projection(view, perspective);
|
|
},
|
|
Err(_) => {},
|
|
}
|
|
|
|
let render_status = self.point_cloud_renderer.render();
|
|
|
|
unsafe {
|
|
gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
|
|
}
|
|
|
|
render_status
|
|
.map_err(|err| PointCloudVisualizerError::PointCloudRendererError(err))
|
|
}
|
|
|
|
///
|
|
///
|
|
///
|
|
fn update_point_clouds(&mut self, mut captures: Vec<Capture>) -> Result<()> {
|
|
if captures.len() != self.num_cameras {
|
|
println!("Length of captures in insufficient (unplug the cameras from USB and retry): {}", captures.len());
|
|
return Ok(());
|
|
}
|
|
|
|
for (camera_index, capture) in captures.drain(0 .. self.num_cameras)
|
|
.into_iter()
|
|
.enumerate() {
|
|
self.update_point_clouds_for_camera(camera_index, capture)?;
|
|
}
|
|
|
|
// TODO: wrt k4a::Image.clone(), k4a should use ref counting. (I did this one other place a few commits ago.
|
|
// Hope it's not ref leaking.)
|
|
let colorizations: Vec<Image> = self.point_cloud_colorizations.iter()
|
|
.map(|img| img.as_ref().map(|img| img.clone()).unwrap())
|
|
.collect();
|
|
|
|
self.point_cloud_renderer.update_point_clouds(
|
|
&colorizations,
|
|
&self.xyz_textures
|
|
).map_err(|err| PointCloudVisualizerError::PointCloudRendererError(err))
|
|
}
|
|
|
|
fn update_point_clouds_for_camera(&mut self, camera_index: usize, capture: Capture) -> Result<()> {
|
|
//
|
|
// Depth Image extractor
|
|
//
|
|
|
|
let mut depth_image = match capture.get_depth_image() {
|
|
Ok(img) => img,
|
|
Err(_e) => {
|
|
// Capture doesn't have depth info. Drop the capture.
|
|
return Err(PointCloudVisualizerError::MissingDepthImage);
|
|
},
|
|
};
|
|
|
|
let maybe_color_image = capture.get_color_image();
|
|
|
|
if self.enable_color_point_cloud {
|
|
if maybe_color_image.is_err() {
|
|
return Err(PointCloudVisualizerError::MissingColorImage);
|
|
}
|
|
if self.colorization_strategy == ColorizationStrategy::Color {
|
|
if let Some(transformed_depth_image) = self.transformed_depth_images.get_mut(camera_index).unwrap().as_mut() { // TODO: TEMP SUPPORT MULTI-CAMERA
|
|
unsafe {
|
|
|
|
let result = k4a_sys::k4a_transformation_depth_image_to_color_camera(
|
|
self.transformation.get_handle(),
|
|
depth_image.get_handle(),
|
|
transformed_depth_image.get_handle(),
|
|
);
|
|
|
|
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
|
|
return Err(PointCloudVisualizerError::DepthToColorConversionFailed);
|
|
}
|
|
|
|
depth_image = transformed_depth_image.clone();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Convert depth image to depth texture
|
|
//
|
|
|
|
let result = self.point_cloud_converters.get(camera_index).unwrap().convert(
|
|
&depth_image,
|
|
&mut self.xyz_textures.get_mut(camera_index).unwrap(), // TODO: TEMP MULTI CAMERA SUPPORT
|
|
camera_index
|
|
);
|
|
|
|
if let Err(err) = result {
|
|
return Err(PointCloudVisualizerError::PointCloudComputeError(err));
|
|
}
|
|
|
|
// TODO: TEMP MULTI CAMERA SUPPORT
|
|
if let Some(mut inner) = self.last_captures.get_mut(camera_index) {
|
|
*inner = Some(capture);
|
|
}
|
|
|
|
if self.colorization_strategy == ColorizationStrategy::Color {
|
|
let color_image = maybe_color_image.expect("logic above should ensure present");
|
|
// TODO: TEMP MULTI CAMERA SUPPORT
|
|
if let Some(mut inner) = self.point_cloud_colorizations.get_mut(camera_index) {
|
|
*inner = Some(color_image);
|
|
}
|
|
|
|
} else {
|
|
// This creates a color spectrum based on depth.
|
|
let dst_length = depth_image.get_width_pixels() * depth_image.get_height_pixels();
|
|
|
|
unsafe {
|
|
// src: DepthPixel
|
|
let src_pixel_buffer = depth_image.get_buffer();
|
|
let typed_src_pixel_buffer = src_pixel_buffer as *const DepthPixel;
|
|
|
|
// dst: BgraPixel
|
|
let dst_pixel_buffer = self.point_cloud_colorizations.get_mut(camera_index).unwrap() // TODO TEMP MULTI-CAMERA SUPPORT
|
|
.as_ref()
|
|
.expect("point cloud color image must be set")
|
|
.get_buffer();
|
|
|
|
let typed_dst_pixel_buffer = dst_pixel_buffer as *mut BgraPixel;
|
|
|
|
for i in 0 .. dst_length as isize {
|
|
let src_pixel = *typed_src_pixel_buffer.offset(i);
|
|
let pixel = colorize_depth_blue_to_red(src_pixel, &self.depth_value_range);
|
|
(*typed_dst_pixel_buffer.offset(i)).red = pixel.red;
|
|
(*typed_dst_pixel_buffer.offset(i)).green = pixel.green;
|
|
(*typed_dst_pixel_buffer.offset(i)).blue = pixel.blue;
|
|
(*typed_dst_pixel_buffer.offset(i)).alpha = pixel.alpha;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
///
|
|
///
|
|
///
|
|
pub fn set_colorization_strategy(&mut self, strategy: ColorizationStrategy) -> Result<()> {
|
|
for i in 0 .. self.num_cameras {
|
|
self.set_colorization_strategy_for_camera(i, strategy)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn set_colorization_strategy_for_camera(&mut self, camera_index: usize, strategy: ColorizationStrategy) -> Result<()> {
|
|
if strategy == ColorizationStrategy::Color && !self.enable_color_point_cloud {
|
|
return Err(PointCloudVisualizerError::UnsupportedMode);
|
|
}
|
|
|
|
self.colorization_strategy = strategy;
|
|
self.point_cloud_renderer.set_enable_shading(strategy == ColorizationStrategy::Shaded);
|
|
|
|
if strategy == ColorizationStrategy::Color {
|
|
let width = self.calibration_data.color_camera_calibration.resolution_width;
|
|
let height = self.calibration_data.color_camera_calibration.resolution_height;
|
|
let stride = width * size_of::<DepthPixel>() as i32;
|
|
|
|
// TODO: TEMP MULTI-CAMERA SUPPORT
|
|
if let Some(mut transformed_depth_image) = self.transformed_depth_images.get_mut(camera_index) {
|
|
*transformed_depth_image = Some(Image::create(
|
|
ImageFormat::Depth16,
|
|
width as u32,
|
|
height as u32,
|
|
stride as u32,
|
|
).expect("Construction should work FIXME"));
|
|
}
|
|
|
|
for point_cloud_converter in self.point_cloud_converters.iter_mut() {
|
|
point_cloud_converter.set_active_xy_table(&self.color_xy_tables.get(camera_index).unwrap())?; // TODO: TEMP MULTI-CAMERA SUPPORT
|
|
}
|
|
|
|
} else {
|
|
let width = self.calibration_data.depth_camera_calibration.resolution_width as u32;
|
|
let height = self.calibration_data.depth_camera_calibration.resolution_height as u32;
|
|
let stride = width as i32 * size_of::<BgraPixel>() as i32;
|
|
|
|
// TODO: TEMP MULTI-CAMERA SUPPORT
|
|
if let Some(mut point_cloud_colorization) = self.point_cloud_colorizations.get_mut(camera_index) {
|
|
*point_cloud_colorization = Some(Image::create(
|
|
ImageFormat::ColorBgra32,
|
|
width,
|
|
height,
|
|
stride as u32,
|
|
).expect("Construction should work FIXME"));
|
|
}
|
|
|
|
for point_cloud_converter in self.point_cloud_converters.iter_mut() {
|
|
point_cloud_converter.set_active_xy_table(&self.depth_xy_tables.get(camera_index).unwrap())?; // TODO: TEMP MULTI-CAMERA SUPPORT
|
|
}
|
|
}
|
|
|
|
self.xyz_textures.get_mut(0).unwrap().reset(); // TODO: TEMP MULTI-CAMERA SUPPORT
|
|
|
|
// TODO: TEMP MULTI-CAMERA SUPPORT
|
|
if let Some(capture) = self.last_captures.get_mut(camera_index).unwrap().as_ref() {
|
|
let capture = (*capture).clone();
|
|
// TODO: TEMP MULTI-CAMERA SUPPORT
|
|
let mut captures = Vec::new();
|
|
captures.push(capture);
|
|
self.update_point_clouds(captures);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|