Re-introduce files from bd18ed9c09
This commit is contained in:
1 parent
61dac586c6
commit
20607c5367
67 files changed
+10088
No files matched your search
@@ -0,0 +1,3 @@
|
||||
*~
|
||||
.idea/
|
||||
target/
|
||||
Generated
+2420
File diff suppressed because it is too large.
Load diff
+51
@@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "cloudcam"
|
||||
version = "0.0.0"
|
||||
edition = "2018"
|
||||
authors = [ "Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>" ]
|
||||
|
||||
[[bin]]
|
||||
name = "cloudcam_visualizer"
|
||||
path = "src/main_opencv.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "cloudcam_points"
|
||||
path = "src/main_points.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "cloudcam_zeromq"
|
||||
path = "src/main_zeromq.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.32"
|
||||
arcball = "1.0.0" # camera
|
||||
byteorder = "1.4.2"
|
||||
bytes = "1.0.1"
|
||||
cgmath = "0.17.0" # for arcball, but probably good for matrices, vecs, etc.
|
||||
clap = "3.0.0-beta.1"
|
||||
clipboard = "0.5" # to support imgui...
|
||||
env_logger = "0.7.1"
|
||||
gl = "0.14.0" # this looks like bare bones OpenGL
|
||||
glfw = "0.37.0" # GL framework - windows, contexts, devices
|
||||
glfw-sys = "3.3.2" # Does this have linear algebra?
|
||||
glium = { version = "0.26.0", default-features = true } # high-level OpenGL abstraction (*not* vanilla OpenGL)
|
||||
gltf = "0.15.2" # glTF loader (more modern OBJ)
|
||||
image = "0.23.8" # all the image formats
|
||||
imgui = "0.3.0" # OpenGL windowing toolkit (experimental bindings)
|
||||
imgui-glium-renderer = "0.3.0"
|
||||
imgui-opengl-renderer = "0.7.0"
|
||||
imgui-sdl2 = "0.9.0" # for imgui-opengl-renderer
|
||||
imgui-winit-support = "0.3.1"
|
||||
k4a-sys-temp = "0.2.3"
|
||||
kinect = "0.0.5"
|
||||
#kinect = { path = "../kinect.rs" }
|
||||
libc = "0.2.68"
|
||||
log = "0.4.11"
|
||||
nalgebra = "0.21.1" # linear algebra and matrices, fantastic docs
|
||||
nalgebra-glm = "0.7.0" # computer graphics-friendly nalgebra, inspired by C++'s glm
|
||||
rand = "0.7.3"
|
||||
sdl2 = "0.33" # for imgui-opengl-renderer
|
||||
tobj = { version = "1.0", features = ["log"] }# Another obj loader (using older version to match `tobj_viewer` binary/crate)
|
||||
winit = "=0.21.0" # cross-platform window management, because imgui
|
||||
zmq = { version = "0.9", features = ['vendored'] }
|
||||
@@ -0,0 +1,179 @@
|
||||
Cloudcam (WIP)
|
||||
==============
|
||||
|
||||
This is a Rust server that polls Kinect camera(s) and sends
|
||||
streaming point clouds to Unreal Engine or some other consumer.
|
||||
|
||||
Right now the implementation is incredibly naive, but in the
|
||||
future we may be able to implement compression, keyframes, and
|
||||
other advanced features.
|
||||
|
||||
This was forked from my original "rust-experiments" repo, so it
|
||||
has a lot of dead code that needs to be culled. Once this is ready
|
||||
it will simply be code that interfaces with Kinect and talks to
|
||||
consumers.
|
||||
|
||||
The binary of interest is `main_zeromq.rs` (`cloudcam_zeromq`).
|
||||
|
||||
Installation / Requirements
|
||||
---------------------------
|
||||
Install the following packages on Linux:
|
||||
|
||||
* clang
|
||||
* cmake
|
||||
* libsdl2-dev
|
||||
* libxcb-xfixes0-dev
|
||||
* GLFW build requirements:
|
||||
* libpthread (eg. libpthread-stubs0-dev)
|
||||
* libx11-dev
|
||||
* libxcursor-dev
|
||||
* libxi-dev
|
||||
* libxinerama-dev
|
||||
* libxrandr-dev
|
||||
|
||||
Supporting devices over USB in Linux
|
||||
------------------------------------
|
||||
Sometimes the cameras just won't work. We need to increase USB memory and
|
||||
copy a device config file.
|
||||
|
||||
Modify this:
|
||||
|
||||
```
|
||||
sudo sh -c 'echo 2047 > /sys/module/usbcore/parameters/usbfs_memory_mb'
|
||||
```
|
||||
|
||||
Grub bootloader modifications:
|
||||
|
||||
```
|
||||
sudo vi /etc/default/grub
|
||||
```
|
||||
|
||||
Then modify this directive to add the following:
|
||||
|
||||
```
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="[KEEP EXISTING PARTS THE SAME] usbcore.usbfs_memory_mb=2047"
|
||||
```
|
||||
|
||||
Then
|
||||
|
||||
```
|
||||
sudo update-grub
|
||||
```
|
||||
|
||||
(Reboot)
|
||||
|
||||
If `sudo k4aviewer` works but non-sudo doesn't,
|
||||
|
||||
```
|
||||
git clone git@github.com:microsoft/Azure-Kinect-Sensor-SDK.git
|
||||
cd Azure-Kinect-Sensor-SDK/
|
||||
sudo cp scripts/99-k4a.rules /etc/udev/rules.d/
|
||||
```
|
||||
|
||||
Then disconnect and reconnect the USB device. It should work now.
|
||||
|
||||
Ubuntu 18.04 Installation
|
||||
-------------------------
|
||||
|
||||
Configure the Microsoft PPA and install the packages:
|
||||
|
||||
```
|
||||
# Configure PPA
|
||||
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
|
||||
sudo apt-add-repository https://packages.microsoft.com/ubuntu/18.04/prod
|
||||
sudo apt-get update
|
||||
|
||||
#Install packages:
|
||||
sudo apt install k4a-tools
|
||||
sudo apt install libk4a1.1-dev
|
||||
```
|
||||
|
||||
|
||||
Ubuntu 20.04 LTS Support
|
||||
------------------------
|
||||
|
||||
Azure Kinect isn't officially supported on 20.04
|
||||
|
||||
mpdroid's comment on October 17, 2020 seems to indicate that there is a workaround:
|
||||
|
||||
https://feedback.azure.com/forums/920053-azure-kinect-dk/suggestions/40368301-support-for-ubuntu-20-04-lts?page=1&per\_page=20
|
||||
|
||||
```
|
||||
These are the steps I followed to install k4a-tools, libk4a and libk4abt on Ubuntu 20.04. The general steps are as outlined in
|
||||
https://docs.microsoft.com/en-us/azure/Kinect-dk/sensor-sdk-download, with a couple of hacks to make things work on 20.04.
|
||||
- use of 18.04 repo, even though OS is 20.04
|
||||
- installed lower versions of tools and libraries (as latest versions of sensor and body tracker don't seem to be compatible on 20.04)
|
||||
|
||||
$ curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
|
||||
$ sudo apt-add-repository https://packages.microsoft.com/ubuntu/18.04/prod
|
||||
$ curl -sSL https://packages.microsoft.com/config/ubuntu/18.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft-prod.list
|
||||
$ curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
|
||||
$ sudo apt-get update
|
||||
$ sudo apt install libk4a1.3-dev
|
||||
$ sudo apt install libk4abt1.0-dev
|
||||
$ sudo apt install k4a-tools=1.3.0
|
||||
|
||||
- Verify sensor library by launching camera viewer
|
||||
|
||||
$ k4aviewer
|
||||
|
||||
- Clone and build [Azure Kinect Samples](https://github.com/microsoft/Azure-Kinect-Samples).
|
||||
- Run `simple_3d_viewer` to verify body tracker works.
|
||||
```
|
||||
|
||||
There will be complaints:
|
||||
|
||||
```
|
||||
# E: The repository 'https://packages.microsoft.com/ubuntu/18.04/prod focal Release' does not have a Release file.
|
||||
# N: Updating from such a repository can't be done securely, and is therefore disabled by default.
|
||||
```
|
||||
|
||||
Just ignore them.
|
||||
|
||||
Webcam
|
||||
------
|
||||
|
||||
Update:
|
||||
|
||||
This seems to work with some combination of reloading the driver (`./webcam/reload_driver.sh`), running VLC
|
||||
(maybe it sets the video buffer mode?), killing firefox, and starting random browsers and going to meet.
|
||||
Eventually it seems to work.
|
||||
|
||||
Docs and software:
|
||||
|
||||
- software: https://github.com/webcamoid/akvcam
|
||||
- wiki: https://github.com/webcamoid/akvcam/wiki/Usage-and-examples
|
||||
|
||||
The configuration I use is as follows (this may have changed with the new ini file):
|
||||
|
||||
- /dev/video0 is the input
|
||||
- /dev/video1 is the output
|
||||
|
||||
Check to see if the driver is okay:
|
||||
|
||||
```
|
||||
v4l2-compliance -d /dev/video0 -f
|
||||
```
|
||||
|
||||
Check properties:
|
||||
|
||||
```
|
||||
v4l2-ctl -d /dev/video0
|
||||
```
|
||||
|
||||
Playback:
|
||||
|
||||
```
|
||||
ffplay /dev/video1
|
||||
```
|
||||
|
||||
Test writing random noise:
|
||||
|
||||
```
|
||||
cat /dev/urandom > /dev/video0
|
||||
```
|
||||
|
||||
This may have the answers for my bugs:
|
||||
|
||||
- https://github.com/webcamoid/akvcam/issues/6
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod obj_loader;
|
||||
pub mod positionable_object;
|
||||
pub mod renderable_object;
|
||||
@@ -0,0 +1,114 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
use glium::{self, Display};
|
||||
use glium::vertex::VertexBufferAny;
|
||||
use std::f32;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ExtractedVertex {
|
||||
pub position: [f32; 3],
|
||||
pub normal: [f32; 3],
|
||||
pub color_diffuse: [f32; 3],
|
||||
pub color_specular: [f32; 4],
|
||||
pub tex_coords: [f32; 2], // [u, v]
|
||||
}
|
||||
|
||||
/// Returns a vertex buffer that should be rendered as `TrianglesList`.
|
||||
/// Adapted from https://github.com/Twinklebear/tobj_viewer/blob/master/src/support/mod.rs
|
||||
pub fn load_wavefront(path: &Path) -> AnyhowResult<Vec<ExtractedVertex>>
|
||||
//pub fn load_wavefront(display: &Display, path: &Path)
|
||||
//-> (VertexBufferAny, f32)
|
||||
{
|
||||
// TODO: This appears to be one of two hooks for the data to be uploaded to OpenGL
|
||||
implement_vertex!(ExtractedVertex, position, normal, color_diffuse, color_specular, tex_coords);
|
||||
|
||||
let mut min_pos = [f32::INFINITY; 3];
|
||||
let mut max_pos = [f32::NEG_INFINITY; 3];
|
||||
let mut vertex_data = Vec::new();
|
||||
|
||||
println!("loading {:?}...", path);
|
||||
let (models, mats) = tobj::load_obj(path)?;
|
||||
println!("loaded {:?}...", path);
|
||||
|
||||
// Just upload the first object in the group
|
||||
for model in &models {
|
||||
let mesh = &model.mesh;
|
||||
//println!("Uploading model: {}", model.name);
|
||||
|
||||
for idx in &mesh.indices {
|
||||
let i = *idx as usize;
|
||||
|
||||
let pos = [
|
||||
mesh.positions[3 * i],
|
||||
mesh.positions[3 * i + 1],
|
||||
mesh.positions[3 * i + 2],
|
||||
];
|
||||
|
||||
let normal = if !mesh.normals.is_empty() {
|
||||
[
|
||||
mesh.normals[3 * i],
|
||||
mesh.normals[3 * i + 1],
|
||||
mesh.normals[3 * i + 2],
|
||||
]
|
||||
} else {
|
||||
[0.0, 0.0, 0.0]
|
||||
};
|
||||
|
||||
let tex_coords = if !mesh.texcoords.is_empty() {
|
||||
// texcoord = [u, v];
|
||||
[
|
||||
mesh.texcoords[i * 2],
|
||||
mesh.texcoords[i * 2 + 1]
|
||||
]
|
||||
} else {
|
||||
[0.0, 0.0]
|
||||
};
|
||||
|
||||
let (color_diffuse, color_specular) = match mesh.material_id {
|
||||
Some(i) => (
|
||||
mats[i].diffuse,
|
||||
[
|
||||
mats[i].specular[0],
|
||||
mats[i].specular[1],
|
||||
mats[i].specular[2],
|
||||
mats[i].shininess,
|
||||
],
|
||||
),
|
||||
None => ([0.8, 0.8, 0.8], [0.15, 0.15, 0.15, 15.0]),
|
||||
};
|
||||
|
||||
vertex_data.push(ExtractedVertex {
|
||||
position: pos,
|
||||
normal: normal,
|
||||
color_diffuse: color_diffuse,
|
||||
color_specular: color_specular,
|
||||
tex_coords: tex_coords,
|
||||
});
|
||||
|
||||
// Update our min/max pos so we can figure out the bounding box of the object
|
||||
// to view it
|
||||
for i in 0..3 {
|
||||
min_pos[i] = f32::min(min_pos[i], pos[i]);
|
||||
max_pos[i] = f32::max(max_pos[i], pos[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vertex_data)
|
||||
|
||||
// TODO: This appears to be the second hook:
|
||||
// Compute scale factor to fit the model with a [-1, 1] bounding box
|
||||
/*let diagonal_len = 6.0;
|
||||
let current_len = f32::powf(max_pos[0] - min_pos[0], 2.0)
|
||||
+ f32::powf(max_pos[1] - min_pos[1], 2.0)
|
||||
+ f32::powf(max_pos[2] - min_pos[2], 2.0);
|
||||
let scale = f32::sqrt(diagonal_len / current_len);
|
||||
println!("Model scaled by {} to fit", scale);
|
||||
(
|
||||
glium::vertex::VertexBuffer::new(display, &vertex_data)
|
||||
.unwrap()
|
||||
.into_vertex_buffer_any(),
|
||||
scale,
|
||||
)*/
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
use crate::assets::renderable_object::RenderableObject;
|
||||
use gl::types::*;
|
||||
use nalgebra::{Vector4, Rotation3, Translation3, Matrix4, Vector3, RealField};
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use crate::opengl::wrapper::uniform::Uniform;
|
||||
|
||||
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
|
||||
pub enum ObjectType {
|
||||
Skybox,
|
||||
Level,
|
||||
Character,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub struct PositionableObject {
|
||||
pub renderable_object: RenderableObject,
|
||||
|
||||
// Transformation data for the object
|
||||
|
||||
pub scale: Vector3<f32>,
|
||||
pub rotation: Vector4<f32>,
|
||||
pub translation: Vector3<f32>,
|
||||
|
||||
//pub rotation_typed: Rotation3<f32>,
|
||||
//pub translation_typed: Translation3<f32>,
|
||||
//pub is_transformed: bool,
|
||||
pub object_type: ObjectType,
|
||||
}
|
||||
|
||||
impl PositionableObject {
|
||||
|
||||
pub fn new(renderable_object: RenderableObject) -> Self {
|
||||
Self {
|
||||
renderable_object,
|
||||
scale: Vector3::new(1.0, 1.0, 1.0),
|
||||
rotation: Vector4::default(),
|
||||
translation: Vector3::default(),
|
||||
//rotation_typed: Rotation3::identity(),
|
||||
//translation_typed: Translation3::identity(),
|
||||
//is_transformed: true,
|
||||
object_type: ObjectType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self, x: f32, y: f32, z: f32) {
|
||||
self.rotation.x = x;
|
||||
self.rotation.y = y;
|
||||
self.rotation.z = z;
|
||||
}
|
||||
|
||||
pub fn rotate_x(&mut self, x: f32) {
|
||||
self.rotation.x = x;
|
||||
}
|
||||
|
||||
pub fn rotate_y(&mut self, y: f32) {
|
||||
self.rotation.y = y;
|
||||
}
|
||||
|
||||
pub fn rotate_z(&mut self, z: f32) {
|
||||
self.rotation.z = z;
|
||||
}
|
||||
|
||||
/// Add pi to x rotation
|
||||
pub fn flip_x(&mut self) {
|
||||
self.rotation.x += f32::pi();
|
||||
}
|
||||
|
||||
/// Add pi to y rotation
|
||||
pub fn flip_y(&mut self) {
|
||||
self.rotation.y += f32::pi();
|
||||
}
|
||||
|
||||
/// Add pi to z rotation
|
||||
pub fn flip_z(&mut self) {
|
||||
self.rotation.z += f32::pi();
|
||||
}
|
||||
|
||||
pub fn increment_rotation_x(&mut self, x: f32) {
|
||||
self.rotation.x += x;
|
||||
}
|
||||
|
||||
pub fn increment_rotation_y(&mut self, y: f32) {
|
||||
self.rotation.y += y;
|
||||
}
|
||||
|
||||
pub fn increment_rotation_z(&mut self, z: f32) {
|
||||
self.rotation.z += z;
|
||||
}
|
||||
|
||||
pub fn scale_nonuniform(&mut self, x: f32, y: f32, z: f32) {
|
||||
self.scale.x = x;
|
||||
self.scale.y = y;
|
||||
self.scale.z = z;
|
||||
}
|
||||
|
||||
pub fn scale(&mut self, scale: f32) {
|
||||
self.scale.x = scale;
|
||||
self.scale.y = scale;
|
||||
self.scale.z = scale;
|
||||
}
|
||||
|
||||
pub fn translate(&mut self, x: f32, y: f32, z: f32) {
|
||||
self.translation.x = x;
|
||||
self.translation.y = y;
|
||||
self.translation.z = z;
|
||||
}
|
||||
|
||||
pub fn translate_x(&mut self, x: f32) {
|
||||
self.translation.x = x;
|
||||
}
|
||||
|
||||
pub fn translate_y(&mut self, y: f32) {
|
||||
self.translation.y = y;
|
||||
}
|
||||
|
||||
pub fn translate_z(&mut self, z: f32) {
|
||||
self.translation.z = z;
|
||||
}
|
||||
|
||||
pub fn draw(&self, model_transform_id: Uniform) {
|
||||
let mut transformation = Matrix4::identity();
|
||||
let rotation = Matrix4::from_euler_angles(self.rotation.x, self.rotation.y, self.rotation.z);
|
||||
|
||||
transformation.append_nonuniform_scaling_mut(&self.scale);
|
||||
transformation = transformation * rotation;
|
||||
transformation.append_translation_mut(&self.translation);
|
||||
|
||||
//println!("matrix: {:?}", transformation);
|
||||
//println!("translation: {:?}", &self.translation);
|
||||
|
||||
let mat_ptr = transformation.as_ptr();
|
||||
|
||||
self.renderable_object.vao.bind();
|
||||
|
||||
unsafe {
|
||||
gl::UniformMatrix4fv(model_transform_id.id(), 1, gl::FALSE, mat_ptr);
|
||||
}
|
||||
|
||||
self.renderable_object.draw(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use anyhow::bail;
|
||||
use crate::assets::obj_loader::{ExtractedVertex, load_wavefront};
|
||||
use crate::opengl::wrapper::buffer::Buffer;
|
||||
use crate::opengl::wrapper::texture::Texture;
|
||||
use crate::opengl::wrapper::vertex_array::VertexArray;
|
||||
use gl::types::*;
|
||||
use std::ffi::{c_void, CString};
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::c_char;
|
||||
use std::path::Path;
|
||||
use std::ptr::null;
|
||||
use image::{DynamicImage, Pixel, Rgba, RgbaImage, ImageBuffer, RgbImage};
|
||||
use crate::opengl::wrapper::uniform::Uniform;
|
||||
use image::imageops::{flip_horizontal, flip_vertical};
|
||||
use crate::opengl::wrapper::attribute::Attribute;
|
||||
|
||||
/// A collection of all the graphics-related data to render: vertices, normals, etc.
|
||||
pub struct RenderableObject {
|
||||
pub vao: VertexArray,
|
||||
pub vertex_buffer: Buffer,
|
||||
pub normal_buffer: Buffer,
|
||||
pub color_buffer: Buffer,
|
||||
pub texture_coords_buffer: Buffer,
|
||||
pub texture: Texture,
|
||||
pub num_vertices: usize,
|
||||
pub buffered: bool,
|
||||
}
|
||||
|
||||
impl RenderableObject {
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
let vao = VertexArray::new_initialized();
|
||||
vao.bind();
|
||||
|
||||
let vertex_buffer = Buffer::new_initialized();
|
||||
let normal_buffer = Buffer::new_initialized();
|
||||
let color_buffer = Buffer::new_initialized();
|
||||
let texture_coords_buffer = Buffer::new_initialized();
|
||||
let texture = Texture::new_initialized();
|
||||
|
||||
Self {
|
||||
vao,
|
||||
vertex_buffer,
|
||||
normal_buffer,
|
||||
color_buffer,
|
||||
texture_coords_buffer,
|
||||
texture,
|
||||
num_vertices: 0,
|
||||
buffered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_wavefront(path: &Path, shader_id: GLuint) -> AnyhowResult<Self> {
|
||||
let vertices = load_wavefront(&path)?;
|
||||
|
||||
let mut renderable_object = RenderableObject::new();
|
||||
renderable_object.load_vertices(&vertices, shader_id);
|
||||
|
||||
renderable_object.load_texture_coordinates(&vertices, shader_id);
|
||||
|
||||
Ok(renderable_object)
|
||||
}
|
||||
|
||||
pub fn load_vertices(&mut self, vertex_data: &Vec<ExtractedVertex>, shader_id: GLuint) {
|
||||
self.vao.bind();
|
||||
self.vertex_buffer.bind_as_array_buffer();
|
||||
|
||||
let size = vertex_data.len() * 3 * size_of::<f32>();
|
||||
let mut vertices : Vec<f32> = Vec::with_capacity(vertex_data.len() * 3);
|
||||
|
||||
for v in vertex_data.into_iter() {
|
||||
vertices.extend(&v.position);
|
||||
}
|
||||
|
||||
self.num_vertices = vertex_data.len();
|
||||
|
||||
println!("Object vertices loaded: {}", vertices.len());
|
||||
|
||||
unsafe {
|
||||
let vertices_ptr = vertices.as_ptr() as *const c_void;
|
||||
|
||||
gl::BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
size as isize,
|
||||
vertices_ptr,
|
||||
gl::STATIC_DRAW,
|
||||
);
|
||||
|
||||
let name : CString = CString::new("position").expect("string is correct");
|
||||
let name_ptr : *const c_char = name.as_ptr() as *const c_char;
|
||||
|
||||
let loc = gl::GetAttribLocation(shader_id, name_ptr);
|
||||
println!("Attrib location: {}", loc);
|
||||
|
||||
gl::EnableVertexAttribArray(loc as u32);
|
||||
|
||||
gl::VertexAttribPointer(loc as u32, 3, gl::FLOAT, gl::FALSE, 0, null());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vertex_type(&mut self, vertex_type: &Uniform) {
|
||||
/*self.vao.bind();
|
||||
let loc = vertex_type.id();
|
||||
println!("Uniform location: {}", loc);
|
||||
|
||||
unsafe {
|
||||
//gl::EnableVertexAttribArray(loc as u32);
|
||||
//gl::VertexAttribIPointer(loc as u32, 1, gl::UNSIGNED_BYTE, 0, null());
|
||||
//gl::VertexAttribI1ui(loc as u32, 0);
|
||||
//gl::Uniform1i(loc, 1);
|
||||
}*/
|
||||
|
||||
//vertex_type.set_uint(0);
|
||||
}
|
||||
|
||||
pub fn load_texture_coordinates(&mut self, vertex_data: &Vec<ExtractedVertex>, shader_id: GLuint) {
|
||||
self.vao.bind();
|
||||
self.texture_coords_buffer.bind_as_array_buffer();
|
||||
|
||||
let size = vertex_data.len() * 2 * size_of::<f32>();
|
||||
let mut tex_coords : Vec<f32> = Vec::with_capacity(vertex_data.len() * 2);
|
||||
|
||||
for v in vertex_data.into_iter() {
|
||||
tex_coords.extend(&v.tex_coords);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let tex_coords_ptr = tex_coords.as_ptr() as *const c_void;
|
||||
|
||||
gl::BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
size as isize,
|
||||
tex_coords_ptr,
|
||||
gl::STATIC_DRAW,
|
||||
);
|
||||
|
||||
let name : CString = CString::new("vTextureCoord").expect("string is correct");
|
||||
let name_ptr : *const c_char = name.as_ptr() as *const c_char;
|
||||
|
||||
let loc = gl::GetAttribLocation(shader_id, name_ptr);
|
||||
println!("Attrib location: {}", loc);
|
||||
|
||||
gl::EnableVertexAttribArray(loc as u32);
|
||||
|
||||
gl::VertexAttribPointer(loc as u32, 2, gl::FLOAT, gl::FALSE, 0, null());
|
||||
|
||||
// TODO: This is in my old OpenGL program, but not the rust package?
|
||||
//gl::TexCoordPointer(2, gl::FLOAT, 0, tex_coords_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Option for 'rgb'/'rgba'
|
||||
// TODO: Option to flip texture on any axis
|
||||
pub fn load_texture(&mut self, filename: &str, texture_uniform: &Uniform) -> AnyhowResult<()> {
|
||||
let img = image::open(&filename)?;
|
||||
|
||||
let mut rgba_image= img.to_rgba();
|
||||
|
||||
//let rgb_image : RgbImage = flip_horizontal(&rgb_image);
|
||||
let rgba_image : RgbaImage = flip_vertical(&rgba_image);
|
||||
|
||||
let width = rgba_image.width();
|
||||
let height = rgba_image.height();
|
||||
|
||||
let img_data = rgba_image.into_raw();
|
||||
let pixel_data = img_data.as_ptr() as *const c_void;
|
||||
|
||||
self.texture.bind_as_texture_2d();
|
||||
|
||||
println!("Loading texture into OpenGL {}x{}...", width, height);
|
||||
|
||||
unsafe {
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
|
||||
|
||||
//gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
|
||||
//gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_BORDER as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_BORDER as i32);
|
||||
|
||||
let border_color : Vec<f32> = vec![0.0, 1.0, 0.0, 1.0];
|
||||
let border_color_ptr = border_color.as_ptr() as *const f32;
|
||||
gl::TexParameterfv(gl::TEXTURE_2D, gl::TEXTURE_BORDER_COLOR, border_color_ptr);
|
||||
|
||||
gl::TexImage2D(
|
||||
gl::TEXTURE_2D,
|
||||
0, // image level (mipmap); 0 is base
|
||||
gl::RGBA as i32, // internal format
|
||||
width as i32,
|
||||
height as i32,
|
||||
0, // border; "must be 0"
|
||||
gl::RGBA, // format of pixels
|
||||
gl::UNSIGNED_BYTE, // pixel data type
|
||||
pixel_data,
|
||||
);
|
||||
|
||||
/*
|
||||
// Checkerboard texture.
|
||||
let pixels : Vec<f32> = vec![
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
1.0, 1.0, 1.0, 1.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
1.0, 1.0, 1.0, 1.0,
|
||||
];
|
||||
|
||||
let pixels_ptr = pixels.as_ptr() as *const c_void;
|
||||
|
||||
gl::TexImage2D(
|
||||
gl::TEXTURE_2D,
|
||||
0, // image level (mipmap); 0 is base
|
||||
gl::RGBA as i32, // internal format
|
||||
2,
|
||||
2,
|
||||
0, // border; "must be 0"
|
||||
gl::RGBA, // format of pixels
|
||||
gl::FLOAT, // pixel data type
|
||||
pixels_ptr,
|
||||
);
|
||||
*/
|
||||
|
||||
gl::GenerateMipmap(gl::TEXTURE_2D);
|
||||
|
||||
println!("texture uniform: {}", texture_uniform.id());
|
||||
|
||||
// TODO: This is in my OpenGL program, but is broken here.
|
||||
//gl::Uniform1i(texture_uniform.id(), 0);
|
||||
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(&self, vao_already_bound: bool) {
|
||||
if !vao_already_bound {
|
||||
self.vao.bind();
|
||||
}
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.texture.id());
|
||||
|
||||
gl::DrawArrays(gl::TRIANGLES, 0, self.num_vertices as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
|
||||
use kinect::Calibration;
|
||||
|
||||
pub trait CaptureProvider {
|
||||
|
||||
/** Return the number of cameras. */
|
||||
fn get_num_cameras(&self) -> usize;
|
||||
|
||||
/**
|
||||
* Take the latest captures, if available, through interior mutability.
|
||||
* This leaves the mutex holding an empty vec.
|
||||
*/
|
||||
fn get_captures(&self) -> Option<Vec<CaptureProxy>>;
|
||||
|
||||
/** Get device calibrations for all cameras. */
|
||||
fn get_calibrations(&self) -> &Vec<Calibration>;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use crate::azure_kinect::capture::device_capturer::CaptureProvider;
|
||||
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
|
||||
use crate::point_cloud::debug::image_proxy::ImageProxy;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::{ImageFormat, Calibration};
|
||||
|
||||
pub struct FakeDeviceCaptureProvider {
|
||||
num_cameras: usize,
|
||||
captures: Vec<CaptureProxy>,
|
||||
all_device_calibrations: Vec<Calibration>,
|
||||
}
|
||||
|
||||
impl FakeDeviceCaptureProvider {
|
||||
pub fn new() -> AnyhowResult<Self> {
|
||||
// TODO: Color dimension stride is wrong (and maybe others)
|
||||
let color = ImageProxy::from_file("output/color_frame_0", 3840, 2160, 0, ImageFormat::ColorBgra32)?;
|
||||
let depth = ImageProxy::from_file("output/depth_frame_0", 640, 576, 1280, ImageFormat::Depth16)?;
|
||||
let frame_1 = CaptureProxy::from_image_proxy_pair(color, depth);
|
||||
|
||||
// TODO: Color dimension stride is wrong (and maybe others)
|
||||
let color = ImageProxy::from_file("output/color_frame_1", 3840, 2160, 0, ImageFormat::ColorBgra32)?;
|
||||
let depth = ImageProxy::from_file("output/depth_frame_1", 640, 576, 1280, ImageFormat::Depth16)?;
|
||||
let frame_2 = CaptureProxy::from_image_proxy_pair(color, depth);
|
||||
|
||||
let mut captures = Vec::new();
|
||||
captures.push(frame_1);
|
||||
captures.push(frame_2);
|
||||
|
||||
let mut calibration = Calibration::default();
|
||||
|
||||
// NB: Experimentally determined "constants" from real devices
|
||||
// These are necessary to compute the depth camera transform into color camera space.
|
||||
calibration.0.color_resolution = 5;
|
||||
calibration.0.depth_mode = 2;
|
||||
calibration.0.depth_camera_calibration.resolution_width = 640;
|
||||
calibration.0.depth_camera_calibration.resolution_height = 576;
|
||||
calibration.0.depth_camera_calibration.metric_radius = 1.7399998;
|
||||
calibration.0.depth_camera_calibration.extrinsics.rotation = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
|
||||
calibration.0.depth_camera_calibration.extrinsics.translation = [0.0, 0.0, 0.0];
|
||||
calibration.0.depth_camera_calibration.intrinsics.type_ = 4;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameter_count = 14;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.cx = 325.4652;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.cy = 339.75952;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.fx = 503.72073;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.fy = 503.82776;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k1 = 5.7966313;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k2 = 4.1091304;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k3 = 0.22066948;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k4 = 6.1226273;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k5 = 6.053665;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k6 = 1.1403816;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.codx = 0.0;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.cody = 0.0;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.p2 = -0.00003910405;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.p1 = -0.00009726079;
|
||||
calibration.0.depth_camera_calibration.intrinsics.parameters.param.metric_radius = 0.0;
|
||||
|
||||
calibration.0.color_camera_calibration.resolution_width = 3840;
|
||||
calibration.0.color_camera_calibration.resolution_height = 2160;
|
||||
calibration.0.color_camera_calibration.metric_radius = 1.7;
|
||||
calibration.0.color_camera_calibration.extrinsics.rotation = [0.99998903, -0.0045318827, 0.001187322, 0.0044161947, 0.9964592, 0.08396203, -0.001563624, -0.08395587, 0.99646825];
|
||||
calibration.0.color_camera_calibration.extrinsics.translation = [-32.02683, -2.3786376, 3.9904723];
|
||||
calibration.0.color_camera_calibration.intrinsics.type_ = 4;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameter_count = 14;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.cx = 1919.9363;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.cy = 1105.1877;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.fx = 1832.5886;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.fy = 1832.6873;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.k1 = 0.4812507;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.k2 = -2.5299993;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.k3 = 1.4068391;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.k4 = 0.36012942;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.k5 = -2.355542;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.k6 = 1.3372931;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.codx = 0.0;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.cody = 0.0;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.p2 = -0.00037526494;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.p1 = 0.00054938474;
|
||||
calibration.0.color_camera_calibration.intrinsics.parameters.param.metric_radius = 0.0;
|
||||
calibration.0.extrinsics = [
|
||||
[
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
|
||||
translation: [0.0, 0.0, 0.0]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.99998903, -0.0045318827, 0.001187322, 0.0044161947, 0.9964592, 0.08396203, -0.001563624, -0.08395587, 0.99646825],
|
||||
translation: [-32.02683, -2.3786376, 3.9904723]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.00908543, 0.1157082, -0.99324167, -0.9999575, -0.0005106917, -0.009206355, -0.001572491, 0.9932831, 0.11569864],
|
||||
translation: [0.0, 0.0, 0.0]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.004534903, 0.112131886, -0.993683, -0.9999893, -0.00040909834, -0.004609848, -0.00092342496, 0.99369323, 0.11212883],
|
||||
translation: [-51.077, 3.2191863, 1.8031791]
|
||||
}
|
||||
],
|
||||
[
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.99998903, 0.0044161947, -0.001563624, -0.0045318827, 0.9964592, -0.08395587, 0.001187322, 0.08396203, 0.99646825],
|
||||
translation: [32.043224, 2.560097, -3.7386374]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [1.0, 0.00000000026193447, 0.0, 0.00000000026193447, 1.0, -0.000000007450581, 0.0, -0.000000007450581, 1.0],
|
||||
translation: [0.0000038146973, 0.0, 0.0]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.007381657, 0.031944036, -0.99946237, -0.9999551, -0.005697875, -0.007567407, -0.005936545, 0.9994734, 0.03190054],
|
||||
translation: [4.300721, -32.00875, 2.0599582]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.0028468627, 0.028323233, -0.99959475, -0.99998194, -0.005210849, -0.0029956135, -0.005293583, 0.9995853, 0.028307885],
|
||||
translation: [-46.929596, -28.807508, 3.8983316]
|
||||
}
|
||||
],
|
||||
[
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.00908543, -0.9999575, -0.001572491, 0.1157082, -0.0005106917, 0.9932831, -0.99324167, -0.009206355, 0.11569864],
|
||||
translation: [0.0, 0.0, 0.0]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.007381657, -0.9999551, -0.005936545, 0.031944036, -0.005697875, 0.9994734, -0.99946237, -0.007567407, 0.03190054],
|
||||
translation: [-32.02683, -2.3786376, 3.9904723]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.99999994, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.99999994],
|
||||
translation: [0.0, 0.0, 0.0]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.99998313, 0.0045562233, -0.003596194, -0.0045539755, 0.99998945, 0.00063277053, 0.0035990402, -0.0006163829, 0.99999326],
|
||||
translation: [-51.077, 3.2191863, 1.8031791]
|
||||
}
|
||||
],
|
||||
[
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.004534903, -0.9999893, -0.00092342496, 0.112131886, -0.00040909834, 0.99369323, -0.993683, -0.004609848, 0.11212883],
|
||||
translation: [3.452446, 3.9368703, -50.941692]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.0028468627, -0.99998194, -0.005293583, 0.028323233, -0.005210849, 0.9995853, -0.99959475, -0.0029956135, 0.028307885],
|
||||
translation: [-28.652746, -2.717628, -47.10723]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.99998313, -0.0045539755, 0.0035990402, 0.0045562233, 0.99998945, -0.0006163829, -0.003596194, 0.00063277053, 0.99999326],
|
||||
translation: [51.08431, -2.9853227, -1.9888868]
|
||||
},
|
||||
k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.99999994, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.99999994],
|
||||
translation: [-0.0000038146973, 0.00000023841858, 0.00000011920929] }
|
||||
]
|
||||
];
|
||||
|
||||
let mut all_device_calibrations = Vec::new();
|
||||
for _ in 0 .. 2 {
|
||||
all_device_calibrations.push(calibration.clone());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
num_cameras: 2,
|
||||
captures,
|
||||
all_device_calibrations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CaptureProvider for FakeDeviceCaptureProvider {
|
||||
fn get_num_cameras(&self) -> usize {
|
||||
self.num_cameras
|
||||
}
|
||||
|
||||
fn get_captures(&self) -> Option<Vec<CaptureProxy>> {
|
||||
let capture_clones = self.captures.iter()
|
||||
.map(|capture| capture.clone())
|
||||
.collect();
|
||||
Some(capture_clones)
|
||||
}
|
||||
|
||||
fn get_calibrations(&self) -> &Vec<Calibration> {
|
||||
&self.all_device_calibrations
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod device_capturer;
|
||||
pub mod fake_device_capturer;
|
||||
pub mod multi_device_capturer;
|
||||
@@ -0,0 +1,263 @@
|
||||
use crate::azure_kinect::capture::device_capturer::CaptureProvider;
|
||||
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::{Device, KinectError, Capture, CaptureError, GetCaptureError, Calibration};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
// Allowing at least 160 microseconds between depth cameras should ensure they do not interfere with one another.
|
||||
const MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC : libc::int32_t = 160;
|
||||
|
||||
pub struct MultiDeviceCapturer {
|
||||
num_cameras: usize,
|
||||
pub primary_device: Device, // TODO: Temporary public viz
|
||||
secondary_devices: Vec<Device>,
|
||||
/// Calibrations for primary + all secondaries in order.
|
||||
all_device_calibrations: Vec<Calibration>,
|
||||
capture_provider: Arc<MultiDeviceCaptureProvider>,
|
||||
}
|
||||
|
||||
pub struct MultiDeviceCaptureProvider {
|
||||
num_cameras: usize,
|
||||
/// Calibrations for primary + all secondaries in order.
|
||||
all_device_calibrations: Vec<Calibration>,
|
||||
captures: Arc<Mutex<Vec<Capture>>>, // TODO: Shouldn't need to wrap in mutex if we wrap struct instead.
|
||||
}
|
||||
|
||||
impl MultiDeviceCapturer {
|
||||
|
||||
// TODO: Don't panic everywere.
|
||||
/// Open the primary and secondary devices.
|
||||
pub fn new(num_devices: u32, limit_secondary_devices: Option<usize>) -> Result<Self, KinectError> {
|
||||
let mut primary_device = None;
|
||||
let mut secondary_devices = Vec::new();
|
||||
let mut calibrations = Vec::new();
|
||||
|
||||
let depth_mode : k4a_sys::k4a_depth_mode_t = 2; //k4a_sys::K4A_DEPTH_MODE_NFOV_UNBINNED;
|
||||
let color_format: k4a_sys::k4a_color_resolution_t = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
|
||||
|
||||
for i in 0..num_devices {
|
||||
let mut device = Device::open(i)?;
|
||||
let jack_status = device.get_synchronization_jack_status()?;
|
||||
|
||||
if jack_status.sync_out_jack_connected && !jack_status.sync_in_jack_connected {
|
||||
if primary_device.is_some() {
|
||||
// NB: This assumes a daisy chain topology.
|
||||
panic!("We already one primary device. We can't have two.");
|
||||
}
|
||||
|
||||
let calibration = device.get_calibration(depth_mode, color_format).unwrap();
|
||||
|
||||
calibrations.push(calibration);
|
||||
primary_device = Some(device);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if !jack_status.sync_in_jack_connected {
|
||||
panic!("Secondary device does not have 'in' jack connected!");
|
||||
}
|
||||
|
||||
let calibration = device.get_calibration(depth_mode, color_format).unwrap();
|
||||
|
||||
secondary_devices.push(device);
|
||||
calibrations.push(calibration);
|
||||
}
|
||||
|
||||
if primary_device.is_none() {
|
||||
panic!("Could not find primary device!");
|
||||
}
|
||||
|
||||
if let Some(limit) = limit_secondary_devices {
|
||||
secondary_devices.truncate(limit);
|
||||
}
|
||||
|
||||
let num_cameras = 1 + secondary_devices.len();
|
||||
|
||||
let primary = primary_device.expect("There must be a primary device");
|
||||
|
||||
let calibrations2 = calibrations.clone();
|
||||
|
||||
Ok(Self {
|
||||
num_cameras,
|
||||
primary_device: primary,
|
||||
secondary_devices,
|
||||
all_device_calibrations: calibrations,
|
||||
capture_provider: Arc::new(MultiDeviceCaptureProvider::new(num_cameras, calibrations2)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the number of cameras
|
||||
pub fn get_num_cameras(&self) -> usize {
|
||||
self.num_cameras
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Per Microsoft's "green screen" example, it's possible to differentially configure things such
|
||||
// as exposure, but we won't consider that at the moment. When we do, there is a lot of good
|
||||
// documentation in the 'green_screen' source code that speaks to important timing delay
|
||||
// considerations.
|
||||
/// Start cameras all with the same configuration
|
||||
pub fn start_cameras(&self) -> Result<(), KinectError> {
|
||||
let secondary_config = get_secondary_device_config();
|
||||
|
||||
// NB: Secondary devices *MUST* be started before the master!
|
||||
for device in self.secondary_devices.iter() {
|
||||
device.start_cameras(secondary_config)?;
|
||||
}
|
||||
|
||||
// Once the secondaries start, we can start the primary.
|
||||
let primary_config = get_primary_device_config();
|
||||
self.primary_device.start_cameras(primary_config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_synchronized_captures(&self) -> Result<Vec<Capture>, GetCaptureError> {
|
||||
let mut captures = Vec::with_capacity(1 + self.secondary_devices.len());
|
||||
|
||||
// TODO: -1 is K4A_WAIT_INDEFINITE
|
||||
let capture = self.primary_device.get_capture(-1)?;
|
||||
captures.push(capture);
|
||||
|
||||
for device in self.secondary_devices.iter() {
|
||||
let capture = device.get_capture(-1)?;
|
||||
captures.push(capture);
|
||||
}
|
||||
|
||||
// TODO TODO TODO - Lots of sync logic goes here
|
||||
// TODO TODO TODO - Lots of sync logic goes here
|
||||
// TODO TODO TODO - Lots of sync logic goes here
|
||||
// TODO TODO TODO - Lots of sync logic goes here
|
||||
|
||||
Ok(captures)
|
||||
}
|
||||
|
||||
/// Get a capture provider for another thread
|
||||
pub fn get_sync_capture_provider(&self) -> Arc<MultiDeviceCaptureProvider> {
|
||||
self.capture_provider.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiDeviceCaptureProvider {
|
||||
pub fn new(num_cameras: usize, all_device_calibrations: Vec<Calibration>) -> Self {
|
||||
Self {
|
||||
num_cameras,
|
||||
all_device_calibrations,
|
||||
captures: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume captures and replace whatever we currently hold.
|
||||
*/
|
||||
pub fn set_captures(&self, captures: Vec<Capture>) {
|
||||
match self.captures.lock() {
|
||||
Ok(mut lock) => {
|
||||
*lock = captures;
|
||||
},
|
||||
Err(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CaptureProvider for MultiDeviceCaptureProvider {
|
||||
|
||||
/** Return the number of cameras. */
|
||||
fn get_num_cameras(&self) -> usize {
|
||||
self.num_cameras
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the latest captures, if available, through interior mutability.
|
||||
* This leaves the mutex holding an empty vec.
|
||||
*/
|
||||
fn get_captures(&self) -> Option<Vec<CaptureProxy>> {
|
||||
self.captures.lock()
|
||||
.ok()
|
||||
.map(|mut v| (*v).split_off(0))
|
||||
.map(|mut captures| captures.into_iter()
|
||||
.map(|capture| CaptureProxy::consume_k4a_capture(capture))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn get_calibrations(&self) -> &Vec<Calibration> {
|
||||
&self.all_device_calibrations
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_capture_thread(capturer: MultiDeviceCapturer) {
|
||||
let mut capture_provider = capturer.get_sync_capture_provider();
|
||||
|
||||
loop {
|
||||
let captures = capturer.get_synchronized_captures()
|
||||
.expect("Should get captures");
|
||||
|
||||
capture_provider.set_captures(captures);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_primary_device_config() -> k4a_sys::k4a_device_configuration_t {
|
||||
let mut config = get_default_device_config();
|
||||
config.wired_sync_mode = k4a_sys::k4a_wired_sync_mode_t_K4A_WIRED_SYNC_MODE_MASTER;
|
||||
config.synchronized_images_only = true;
|
||||
|
||||
// Two depth images should be seperated by MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC to ensure the depth imaging
|
||||
// sensor doesn't interfere with the other. To accomplish this the master depth image captures
|
||||
// (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) before the color image, and the subordinate camera captures its
|
||||
// depth image (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) after the color image. This gives us two depth
|
||||
// images centered around the color image as closely as possible.
|
||||
config.depth_delay_off_color_usec = -(MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2);
|
||||
|
||||
// TODO: These are the old values I used that are NOT in 'green_screen'.
|
||||
// They may need to be adjusted
|
||||
config.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
|
||||
config.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED;
|
||||
//config.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn get_secondary_device_config() -> k4a_sys::k4a_device_configuration_t {
|
||||
let mut config = get_default_device_config();
|
||||
config.wired_sync_mode = k4a_sys::k4a_wired_sync_mode_t_K4A_WIRED_SYNC_MODE_SUBORDINATE;
|
||||
|
||||
// Two depth images should be seperated by MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC to ensure the depth imaging
|
||||
// sensor doesn't interfere with the other. To accomplish this the master depth image captures
|
||||
// (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) before the color image, and the subordinate camera captures its
|
||||
// depth image (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) after the color image. This gives us two depth
|
||||
// images centered around the color image as closely as possible.
|
||||
config.depth_delay_off_color_usec = MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2;
|
||||
|
||||
// TODO: These are the old values I used that are NOT in 'green_screen'.
|
||||
// They may need to be adjusted
|
||||
config.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
|
||||
config.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED;
|
||||
//config.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
|
||||
/// Sensible shared defaults
|
||||
fn get_default_device_config() -> k4a_sys::k4a_device_configuration_t {
|
||||
k4a_sys::k4a_device_configuration_t {
|
||||
color_format : k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_BGRA32,
|
||||
color_resolution : k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_720P,
|
||||
// No need for depth during calibration
|
||||
// TODO: We need it.
|
||||
depth_mode : k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_UNBINNED,
|
||||
// Don't use all USB bandwidth
|
||||
// TODO: This will be a problem.
|
||||
camera_fps : k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_15,
|
||||
// Must be zero for master
|
||||
subordinate_delay_off_master_usec : 0,
|
||||
synchronized_images_only : true,
|
||||
|
||||
// These fields were not set and come from `K4A_DEVICE_CONFIG_INIT_DISABLE_ALL`:
|
||||
depth_delay_off_color_usec: 0,
|
||||
disable_streaming_indicator: false,
|
||||
wired_sync_mode: k4a_sys::k4a_wired_sync_mode_t_K4A_WIRED_SYNC_MODE_STANDALONE,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,763 @@
|
||||
//! Wrappers for the k4a-sys crate to make it easier to use.
|
||||
//! I tried using the open source 'k4a-rs' crate from the same author,
|
||||
//! but it's in a bad state and won't compile against any version of 'k4a-sys'.
|
||||
|
||||
use std::mem::MaybeUninit;
|
||||
use std::{ptr, fmt};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use k4a_sys;
|
||||
use std::error::Error;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
/*pub fn device_open() {
|
||||
|
||||
k4a_sys::k4a_device_open(k4a_sys::K4A_DEVICE_DEFAULT)
|
||||
if k4a_device_open(device_idx, &mut device) != k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
|
||||
println!("Failed to open device #{}", device_idx);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// TODO: this is kind of lame.
|
||||
/// A library error
|
||||
#[derive(Debug)]
|
||||
pub enum KinectError {
|
||||
UnableToOpen { error_code: u32 },
|
||||
UnableToGetSerialNumber,
|
||||
UnableToStartCameras { error_code: u32 },
|
||||
UnableToCreateImage { error_code: u32 },
|
||||
UnableToGetSyncJackStatus { error_code: u32 },
|
||||
}
|
||||
|
||||
/// A Kinect Device Handle
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
device_pointer: k4a_sys::k4a_device_t,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// Get the number of installed devices
|
||||
pub fn get_installed_count() -> u32 {
|
||||
unsafe {
|
||||
k4a_sys::k4a_device_get_installed_count()
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a device with the given index
|
||||
pub fn open(device_index: u32) -> Result<Self, KinectError> {
|
||||
let mut device_pointer: k4a_sys::k4a_device_t = ptr::null_mut();
|
||||
unsafe {
|
||||
let result = k4a_sys::k4a_device_open(device_index, &mut device_pointer);
|
||||
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
|
||||
return Err(KinectError::UnableToOpen { error_code: result })
|
||||
}
|
||||
}
|
||||
Ok(Device {
|
||||
device_pointer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch the device serial number.
|
||||
pub fn get_serial_number(&self) -> Result<String, KinectError> {
|
||||
// First we interrogate the serial number size.
|
||||
let mut serial_number_length: usize = 0;
|
||||
|
||||
let result = unsafe {
|
||||
k4a_sys::k4a_device_get_serialnum(self.device_pointer, ptr::null_mut(), &mut serial_number_length)
|
||||
};
|
||||
|
||||
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_TOO_SMALL {
|
||||
return Err(KinectError::UnableToGetSerialNumber);
|
||||
}
|
||||
|
||||
// Now we request to fill a serial number buffer.
|
||||
let mut serial_number = vec![0i8; serial_number_length];
|
||||
let serial_number_ptr = (&mut serial_number).as_mut_ptr();
|
||||
|
||||
let result = unsafe {
|
||||
k4a_sys::k4a_device_get_serialnum(self.device_pointer, serial_number_ptr, &mut serial_number_length)
|
||||
};
|
||||
|
||||
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
|
||||
return Err(KinectError::UnableToGetSerialNumber);
|
||||
}
|
||||
|
||||
// NB: Library shouldn't be returning i8's
|
||||
let serial_number = serial_number.iter().map(|v| *v as u8).collect();
|
||||
|
||||
String::from_utf8(serial_number)
|
||||
.map(|s| s.trim_matches(char::from(0)).into()) // Remove trailing null byte
|
||||
.map_err(|_| KinectError::UnableToGetSerialNumber)
|
||||
}
|
||||
|
||||
/// Get the device synchronization jack statuses.
|
||||
/// Each device has an 'in' jack and an 'out' jack.
|
||||
pub fn get_synchronization_jack_status(&self) -> Result<SynchronizationJackStatus, KinectError> {
|
||||
let mut sync_in_jack_connected = false;
|
||||
let mut sync_out_jack_connected = false;
|
||||
|
||||
let result = unsafe {
|
||||
k4a_sys::k4a_device_get_sync_jack(self.device_pointer,
|
||||
&mut sync_in_jack_connected, &mut sync_out_jack_connected)
|
||||
};
|
||||
|
||||
if result != k4a_sys::k4a_result_t_K4A_RESULT_SUCCEEDED {
|
||||
return Err(KinectError::UnableToGetSyncJackStatus { error_code: result });
|
||||
}
|
||||
|
||||
Ok(SynchronizationJackStatus {
|
||||
sync_in_jack_connected,
|
||||
sync_out_jack_connected,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: having a 'DeviceConfigurations' struct that goes unused is kind of gross.
|
||||
/// Start the cameras
|
||||
pub fn start_cameras(&self,
|
||||
device_config: k4a_sys::k4a_device_configuration_t)
|
||||
-> Result<(), KinectError>
|
||||
{
|
||||
let result = unsafe {
|
||||
k4a_sys::k4a_device_start_cameras(self.device_pointer, &device_config)
|
||||
};
|
||||
|
||||
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
|
||||
return Err(KinectError::UnableToStartCameras { error_code: result });
|
||||
}
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// TODO: More sensible defaults, or get rid of this entirely.
|
||||
/// Start the cameras.
|
||||
pub fn start_cameras_default_config(&self) -> Result<(), KinectError> {
|
||||
let mut device_config = DeviceConfiguration::new();
|
||||
// NB: Although the Kinect docs say this format isn't natively supported by the color camera
|
||||
// and that extra CPU is required, this is the only color mode supported by 'k4aviewer' 3D view.
|
||||
device_config.0.color_format = k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_BGRA32;
|
||||
device_config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
|
||||
device_config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED;
|
||||
device_config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
|
||||
|
||||
self.start_cameras(device_config.0)
|
||||
}
|
||||
|
||||
/// Stops the color and depth camera capture.
|
||||
///
|
||||
/// The streaming of individual sensors stops as a result of this call. Once called,
|
||||
/// k4a_device_start_cameras() may be called again to resume sensor streaming.
|
||||
/// This function may be called while another thread is blocking in k4a_device_get_capture().
|
||||
/// Calling this function while another thread is in that function will result in that function
|
||||
/// returning a failure.
|
||||
pub fn stop_cameras(&self) {
|
||||
unsafe {
|
||||
k4a_sys::k4a_device_stop_cameras(self.device_pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get capture and return a new buffer.
|
||||
pub fn get_capture(&self, timeout_ms: i32) -> Result<Capture, GetCaptureError> {
|
||||
let mut capture_buffer: k4a_sys::k4a_capture_t = ptr::null_mut();
|
||||
self.get_capture_buffered(&mut capture_buffer, timeout_ms)
|
||||
.map(|_| Capture(capture_buffer)) // TODO: Can capture be null?
|
||||
}
|
||||
|
||||
/// Get capture and reuse an existing buffer.
|
||||
pub fn get_capture_buffered(&self, capture_buffer: &mut k4a_sys::k4a_capture_t, timeout_ms: i32)
|
||||
-> Result<(), GetCaptureError>
|
||||
{
|
||||
let result = unsafe {
|
||||
k4a_sys::k4a_device_get_capture(self.device_pointer, capture_buffer, timeout_ms)
|
||||
};
|
||||
|
||||
match result {
|
||||
k4a_sys::k4a_wait_result_t_K4A_WAIT_RESULT_SUCCEEDED => { /* ok, continue */ },
|
||||
k4a_sys::k4a_wait_result_t_K4A_WAIT_RESULT_TIMEOUT => {
|
||||
return Err(GetCaptureError::TimeoutError);
|
||||
},
|
||||
k4a_sys::k4a_wait_result_t_K4A_WAIT_RESULT_FAILED => {
|
||||
return Err(GetCaptureError::TimeoutError);
|
||||
}
|
||||
_ => {
|
||||
return Err(GetCaptureError::UnknownError(result));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the camera calibration for the entire Azure Kinect device.
|
||||
///
|
||||
/// The calibration represents the data needed to transform between the camera views and may be
|
||||
/// different for each operating depth_mode and color_resolution the device is configured to
|
||||
/// operate in.
|
||||
/// The calibration output is used as input to all calibration and transformation functions.
|
||||
pub fn get_calibration(&self,
|
||||
depth_mode: k4a_sys::k4a_depth_mode_t,
|
||||
color_resolution: k4a_sys::k4a_color_resolution_t)
|
||||
-> Result<Calibration, GetCalibrationError>
|
||||
{
|
||||
// TODO: Why isn't the way I've been using to init structures before still working?
|
||||
//let mut calibration_buffer: k4a_sys::k4a_calibration_t = ptr::null_mut();
|
||||
/*let mut calibration_buffer: k4a_sys::k4a_calibration_t = k4a_sys::k4a_calibration_t {
|
||||
color_camera_calibration: 0,
|
||||
color_resolution: 0,
|
||||
depth_camera_calibration: 0,
|
||||
depth_mode: 0,
|
||||
extrinsics: [0,0,0,0],
|
||||
};*/
|
||||
|
||||
unsafe {
|
||||
let mut calibration_buffer: MaybeUninit<k4a_sys::k4a_calibration_t> = MaybeUninit::uninit();
|
||||
|
||||
let result = k4a_sys::k4a_device_get_calibration(
|
||||
self.device_pointer,
|
||||
depth_mode,
|
||||
color_resolution,
|
||||
calibration_buffer.as_mut_ptr(),
|
||||
);
|
||||
|
||||
match result {
|
||||
k4a_sys::k4a_result_t_K4A_RESULT_SUCCEEDED => { /* ok, continue */ },
|
||||
k4a_sys::k4a_result_t_K4A_RESULT_FAILED => {
|
||||
return Err(GetCalibrationError::FailedError);
|
||||
},
|
||||
_ => {
|
||||
return Err(GetCalibrationError::UnknownError(result));
|
||||
},
|
||||
}
|
||||
|
||||
let handle = calibration_buffer.assume_init();
|
||||
let calibration = Calibration(handle);
|
||||
|
||||
Ok(calibration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronization jack status.
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
pub struct SynchronizationJackStatus {
|
||||
pub sync_in_jack_connected: bool,
|
||||
pub sync_out_jack_connected: bool,
|
||||
}
|
||||
|
||||
/// Errors for GetCapture
|
||||
#[derive(Debug)]
|
||||
pub enum GetCaptureError {
|
||||
TimeoutError,
|
||||
FailedError,
|
||||
UnknownError(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CaptureError {
|
||||
NullCapture,
|
||||
}
|
||||
|
||||
impl fmt::Display for CaptureError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "CaptureError")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CaptureError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors for GetCalibration
|
||||
#[derive(Debug)]
|
||||
pub enum GetCalibrationError {
|
||||
FailedError,
|
||||
UnknownError(u32),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Calibration(pub k4a_sys::k4a_calibration_t);
|
||||
|
||||
impl Calibration {
|
||||
pub fn default() -> Self {
|
||||
let extrinsics = k4a_sys::_k4a_calibration_extrinsics_t {
|
||||
rotation: [0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32],
|
||||
translation: [0.0f32, 0.0f32, 0.0f32],
|
||||
};
|
||||
|
||||
let extrinsics_4 = [
|
||||
extrinsics.clone(),
|
||||
extrinsics.clone(),
|
||||
extrinsics.clone(),
|
||||
extrinsics.clone(),
|
||||
];
|
||||
|
||||
let extrinsics_4_4 = [
|
||||
extrinsics_4.clone(),
|
||||
extrinsics_4.clone(),
|
||||
extrinsics_4.clone(),
|
||||
extrinsics_4.clone(),
|
||||
];
|
||||
|
||||
let camera_calibration = k4a_sys::_k4a_calibration_camera_t {
|
||||
resolution_width: 0,
|
||||
resolution_height: 0,
|
||||
metric_radius: 0.0f32,
|
||||
extrinsics: extrinsics.clone(),
|
||||
intrinsics: k4a_sys::_k4a_calibration_intrinsics_t {
|
||||
type_: 0,
|
||||
parameter_count: 0,
|
||||
parameters: k4a_sys::k4a_calibration_intrinsic_parameters_t {
|
||||
param: k4a_sys::k4a_calibration_intrinsic_parameters_t__param {
|
||||
cx: 0.0f32,
|
||||
cy: 0.0f32,
|
||||
fx: 0.0f32,
|
||||
fy: 0.0f32,
|
||||
k1: 0.0f32,
|
||||
k2: 0.0f32,
|
||||
k3: 0.0f32,
|
||||
k4: 0.0f32,
|
||||
k5: 0.0f32,
|
||||
k6: 0.0f32,
|
||||
codx: 0.0f32,
|
||||
cody: 0.0f32,
|
||||
p2: 0.0f32,
|
||||
p1: 0.0f32,
|
||||
metric_radius: 0.0f32,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let mut calibration = k4a_sys::k4a_calibration_t {
|
||||
color_camera_calibration: camera_calibration.clone(),
|
||||
depth_camera_calibration: camera_calibration.clone(),
|
||||
color_resolution: 0,
|
||||
depth_mode: 0,
|
||||
extrinsics: extrinsics_4_4,
|
||||
};
|
||||
|
||||
Self(calibration)
|
||||
}
|
||||
|
||||
// TODO: Make this the `Debug` trait output instead.
|
||||
pub fn debug_print(&self) {
|
||||
println!("===== CALIBRATION =====");
|
||||
println!("\t Color resolution: {}", self.0.color_resolution);
|
||||
println!("\t Depth mode: {}", self.0.depth_mode);
|
||||
println!("\t Extrinsics: {:?}", self.0.extrinsics);
|
||||
|
||||
println!("\t depth.resolution_width: {}", self.0.depth_camera_calibration.resolution_width);
|
||||
println!("\t depth.resolution_height: {}", self.0.depth_camera_calibration.resolution_height);
|
||||
println!("\t depth.metric_radius: {}", self.0.depth_camera_calibration.metric_radius);
|
||||
println!("\t depth.extrinsics: {:?}", self.0.depth_camera_calibration.extrinsics);
|
||||
println!("\t depth.intrinsics.type: {}", self.0.depth_camera_calibration.intrinsics.type_);
|
||||
println!("\t depth.intrinsics.parameter_count: {}", self.0.depth_camera_calibration.intrinsics.parameter_count);
|
||||
unsafe {
|
||||
// NB: This is a union field, so we have to use unsafe access
|
||||
println!("\t depth.intrinsics.parameters.param.cx: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.cx);
|
||||
println!("\t depth.intrinsics.parameters.param.cy: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.cy);
|
||||
println!("\t depth.intrinsics.parameters.param.fx: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.fx);
|
||||
println!("\t depth.intrinsics.parameters.param.fy: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.fy);
|
||||
println!("\t depth.intrinsics.parameters.param.k1: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k1);
|
||||
println!("\t depth.intrinsics.parameters.param.k2: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k2);
|
||||
println!("\t depth.intrinsics.parameters.param.k3: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k3);
|
||||
println!("\t depth.intrinsics.parameters.param.k4: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k4);
|
||||
println!("\t depth.intrinsics.parameters.param.k5: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k5);
|
||||
println!("\t depth.intrinsics.parameters.param.k6: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k6);
|
||||
println!("\t depth.intrinsics.parameters.param.codx: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.codx);
|
||||
println!("\t depth.intrinsics.parameters.param.cody: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.cody);
|
||||
println!("\t depth.intrinsics.parameters.param.p2: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.p2);
|
||||
println!("\t depth.intrinsics.parameters.param.p1: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.p1);
|
||||
println!("\t depth.intrinsics.parameters.param.metric_radius: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.metric_radius);
|
||||
}
|
||||
println!("\t color.resolution_width: {}", self.0.color_camera_calibration.resolution_width);
|
||||
println!("\t color.resolution_height: {}", self.0.color_camera_calibration.resolution_height);
|
||||
println!("\t color.metric_radius: {}", self.0.color_camera_calibration.metric_radius);
|
||||
println!("\t color.extrinsics: {:?}", self.0.color_camera_calibration.extrinsics);
|
||||
println!("\t color.intrinsics.type: {}", self.0.color_camera_calibration.intrinsics.type_);
|
||||
println!("\t color.intrinsics.parameter_count: {}", self.0.color_camera_calibration.intrinsics.parameter_count);
|
||||
unsafe {
|
||||
// NB: This is a union field, so we have to use unsafe access
|
||||
println!("\t color.intrinsics.parameters.param.cx: {}", self.0.color_camera_calibration.intrinsics.parameters.param.cx);
|
||||
println!("\t color.intrinsics.parameters.param.cy: {}", self.0.color_camera_calibration.intrinsics.parameters.param.cy);
|
||||
println!("\t color.intrinsics.parameters.param.fx: {}", self.0.color_camera_calibration.intrinsics.parameters.param.fx);
|
||||
println!("\t color.intrinsics.parameters.param.fy: {}", self.0.color_camera_calibration.intrinsics.parameters.param.fy);
|
||||
println!("\t color.intrinsics.parameters.param.k1: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k1);
|
||||
println!("\t color.intrinsics.parameters.param.k2: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k2);
|
||||
println!("\t color.intrinsics.parameters.param.k3: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k3);
|
||||
println!("\t color.intrinsics.parameters.param.k4: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k4);
|
||||
println!("\t color.intrinsics.parameters.param.k5: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k5);
|
||||
println!("\t color.intrinsics.parameters.param.k6: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k6);
|
||||
println!("\t color.intrinsics.parameters.param.codx: {}", self.0.color_camera_calibration.intrinsics.parameters.param.codx);
|
||||
println!("\t color.intrinsics.parameters.param.cody: {}", self.0.color_camera_calibration.intrinsics.parameters.param.cody);
|
||||
println!("\t color.intrinsics.parameters.param.p2: {}", self.0.color_camera_calibration.intrinsics.parameters.param.p2);
|
||||
println!("\t color.intrinsics.parameters.param.p1: {}", self.0.color_camera_calibration.intrinsics.parameters.param.p1);
|
||||
println!("\t color.intrinsics.parameters.param.metric_radius: {}", self.0.color_camera_calibration.intrinsics.parameters.param.metric_radius);
|
||||
}
|
||||
println!("==========");
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapted from k4a-sys. Represents a capture.
|
||||
#[derive(Debug)]
|
||||
pub struct Capture(pub k4a_sys::k4a_capture_t);
|
||||
|
||||
impl Capture {
|
||||
pub fn get_depth_image(&self) -> Result<Image, CaptureError> {
|
||||
let image = unsafe {
|
||||
k4a_sys::k4a_capture_get_depth_image(self.0)
|
||||
};
|
||||
if image.is_null() {
|
||||
return Err(CaptureError::NullCapture);
|
||||
}
|
||||
Ok(Image(image))
|
||||
}
|
||||
|
||||
pub fn get_color_image(&self) -> Result<Image, CaptureError> {
|
||||
let image = unsafe {
|
||||
k4a_sys::k4a_capture_get_color_image(self.0)
|
||||
};
|
||||
if image.is_null() {
|
||||
return Err(CaptureError::NullCapture);
|
||||
}
|
||||
Ok(Image(image))
|
||||
}
|
||||
|
||||
pub fn get_ir_image(&self) -> Result<Image, CaptureError> {
|
||||
let image = unsafe {
|
||||
k4a_sys::k4a_capture_get_ir_image(self.0)
|
||||
};
|
||||
if image.is_null() {
|
||||
return Err(CaptureError::NullCapture);
|
||||
}
|
||||
Ok(Image(image))
|
||||
}
|
||||
|
||||
/// Returns the underlying opaque handle *without* an additional refcount.
|
||||
/// Do not deallocate it.
|
||||
pub fn get_handle(&self) -> k4a_sys::k4a_capture_t {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a libk4a capture refcount on every drop.
|
||||
/// When the refcount drops to zero, the capture goes away.
|
||||
impl Drop for Capture {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
k4a_sys::k4a_capture_release(self.0);
|
||||
self.0 = null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles are refcounted by libk4a. The final reference is destroyed
|
||||
impl Clone for Capture {
|
||||
fn clone(&self) -> Self {
|
||||
// We must increment the refcount.
|
||||
let handle = self.get_handle();
|
||||
unsafe {
|
||||
k4a_sys::k4a_capture_reference(handle);
|
||||
}
|
||||
Self {
|
||||
0: handle,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_from(&mut self, source: &Self) {
|
||||
let handle = source.get_handle();
|
||||
unsafe {
|
||||
k4a_sys::k4a_capture_reference(handle);
|
||||
}
|
||||
unsafe {
|
||||
// drop refcount, potentially releasing if reached zero
|
||||
k4a_sys::k4a_capture_release(self.0);
|
||||
}
|
||||
self.0 = handle;
|
||||
}
|
||||
}
|
||||
|
||||
/// FIXME FIXME FIXME: WHY IS THIS NOT SEND WITH ARC<MUTEX<T>>!?
|
||||
/// We are making k4a_sys::k4a_capture_t Send only when wrapped with Arc<Mutex<>>,
|
||||
/// but the compiler can't figure that out. Freaky gross.
|
||||
///
|
||||
/// "Rust automatically determines whether a type is Send and/or Sync. Anything that has a
|
||||
/// raw ptr inside, which is what you have here, is considered !Send and !Sync. This isn’t because
|
||||
/// it automatically means the type is unsafe in Send/Sync terms, but rather more like a lint for
|
||||
/// the code author: they need to determine the safety themselves, and then if they’re safe,
|
||||
/// manually impl Send and/or Sync, as appropriate, for the type."
|
||||
///
|
||||
/// Actually Rust is doing a good job here.
|
||||
///
|
||||
/// https://users.rust-lang.org/t/solved-how-to-move-non-send-between-threads-or-an-alternative/19928/11
|
||||
unsafe impl Send for Capture{}
|
||||
|
||||
/// TODO: We also need to send device
|
||||
unsafe impl Send for Device{}
|
||||
|
||||
/// Adapted from k4a-sys. Represents an image within a capture.
|
||||
#[derive(Debug)]
|
||||
pub struct Image(pub k4a_sys::k4a_image_t);
|
||||
|
||||
impl Image {
|
||||
|
||||
/// Create a blank image.
|
||||
///
|
||||
/// This function is used to create images of formats that have consistent stride. The function
|
||||
/// is not suitable for compressed formats that may not be represented by the same number of bytes
|
||||
/// per line.
|
||||
///
|
||||
/// For most image formats, the function will allocate an image buffer of size
|
||||
/// height_pixels * stride_bytes. Buffers K4A_IMAGE_FORMAT_COLOR_NV12 format will allocate an
|
||||
/// additional height_pixels / 2 set of lines (each of stride_bytes). This function cannot be used
|
||||
/// to allocate K4A_IMAGE_FORMAT_COLOR_MJPG buffers.
|
||||
///
|
||||
/// To create an image object without the API allocating memory, or to represent an image that has
|
||||
/// a non-deterministic stride, use k4a_image_create_from_buffer().
|
||||
///
|
||||
/// The k4a_image_t is created with a reference count of 1.
|
||||
///
|
||||
/// When finished using the created image, release it with k4a_image_release.
|
||||
///
|
||||
/// stride_bytes - The number of bytes per horizontal line of the image. If set to 0, the stride
|
||||
/// will be set to the minimum size given the format and width_pixels.
|
||||
///
|
||||
pub fn create(format: ImageFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride_bytes: u32)
|
||||
-> Result<Self, KinectError>
|
||||
{
|
||||
let mut handle = null_mut();
|
||||
|
||||
let result = unsafe {
|
||||
k4a_sys::k4a_image_create(
|
||||
format as k4a_sys::k4a_image_format_t,
|
||||
width as i32,
|
||||
height as i32,
|
||||
stride_bytes as i32,
|
||||
&mut handle
|
||||
)
|
||||
};
|
||||
|
||||
if result != k4a_sys::k4a_result_t_K4A_RESULT_SUCCEEDED {
|
||||
return Err(KinectError::UnableToCreateImage { error_code: result });
|
||||
}
|
||||
|
||||
Ok(Image(handle))
|
||||
}
|
||||
|
||||
pub fn get_height_pixels(&self) -> usize {
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_get_height_pixels(self.0) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_width_pixels(&self) -> usize {
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_get_width_pixels(self.0) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_stride_bytes(&self) -> usize {
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_get_stride_bytes(self.0) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> usize {
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_get_size(self.0) // returns size_t
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_buffer(&self) -> *mut u8 {
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_get_buffer(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this function to determine the format of the image buffer.
|
||||
/// This function is not expected to fail, all k4a_image_t's are created with a
|
||||
/// known format. If the image_handle is invalid, the function will return
|
||||
/// K4A_IMAGE_FORMAT_CUSTOM.
|
||||
pub fn get_format(&self) -> ImageFormat {
|
||||
let format = unsafe {
|
||||
k4a_sys::k4a_image_get_format(self.0)
|
||||
};
|
||||
format.into()
|
||||
}
|
||||
|
||||
/// Returns the underlying opaque handle *without* an additional refcount.
|
||||
/// Do not deallocate it.
|
||||
pub fn get_handle(&self) -> k4a_sys::k4a_image_t {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a libk4a image refcount on every drop.
|
||||
/// When the refcount drops to zero, the image goes away.
|
||||
impl Drop for Image {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_release(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles are refcounted by libk4a. The final reference is destroyed
|
||||
impl Clone for Image {
|
||||
fn clone(&self) -> Self {
|
||||
// We must increment the refcount.
|
||||
let handle = self.get_handle();
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_reference(handle);
|
||||
}
|
||||
Self {
|
||||
0: handle,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_from(&mut self, source: &Self) {
|
||||
let handle = source.get_handle();
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_reference(handle);
|
||||
}
|
||||
unsafe {
|
||||
// drop refcount, potentially releasing if reached zero
|
||||
k4a_sys::k4a_image_release(self.0);
|
||||
}
|
||||
self.0 = handle;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub enum ImageFormat {
|
||||
ColorMjpg,
|
||||
ColorNv12,
|
||||
ColorYuy2,
|
||||
ColorBgra32,
|
||||
/// Depth image type DEPTH16.
|
||||
/// Each pixel of DEPTH16 data is two bytes of little endian unsigned depth data.
|
||||
/// The unit of the data is in millimeters from the origin of the camera.
|
||||
/// Stride indicates the length of each line in bytes and should be used to determine
|
||||
/// the start location of each line of the image in memory.
|
||||
Depth16,
|
||||
Ir16,
|
||||
Custom8,
|
||||
Custom16,
|
||||
Custom,
|
||||
UnknownFormatError, // FIXME: Just return Result<T>?
|
||||
}
|
||||
|
||||
impl ImageFormat {
|
||||
pub fn to_k4a(&self) -> k4a_sys::k4a_image_format_t {
|
||||
match self {
|
||||
ImageFormat::ColorMjpg => 0,
|
||||
ImageFormat::ColorNv12 => 1,
|
||||
ImageFormat::ColorYuy2 => 2,
|
||||
ImageFormat::ColorBgra32 => 3,
|
||||
ImageFormat::Depth16 => 4,
|
||||
ImageFormat::Ir16 => 5,
|
||||
ImageFormat::Custom8 => 6,
|
||||
ImageFormat::Custom16 => 7,
|
||||
ImageFormat::Custom => 8,
|
||||
ImageFormat::UnknownFormatError => 255, // TODO: Better handling?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<k4a_sys::k4a_image_format_t> for ImageFormat {
|
||||
fn from(format: k4a_sys::k4a_image_format_t) -> Self {
|
||||
match format {
|
||||
0 => ImageFormat::ColorMjpg,
|
||||
1 => ImageFormat::ColorNv12,
|
||||
2 => ImageFormat::ColorYuy2,
|
||||
3 => ImageFormat::ColorBgra32,
|
||||
4 => ImageFormat::Depth16,
|
||||
5 => ImageFormat::Ir16,
|
||||
6 => ImageFormat::Custom8,
|
||||
7 => ImageFormat::Custom16,
|
||||
8 => ImageFormat::Custom,
|
||||
_ => ImageFormat::UnknownFormatError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deallocate open device handles
|
||||
impl Drop for Device {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
k4a_sys::k4a_device_close(self.device_pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copied from k4a-sys
|
||||
pub struct DeviceConfiguration (k4a_sys::k4a_device_configuration_t);
|
||||
|
||||
/// Copied from k4a-sys
|
||||
impl DeviceConfiguration {
|
||||
pub fn new() -> Self {
|
||||
Self (k4a_sys::k4a_device_configuration_t {
|
||||
color_format: k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_MJPG,
|
||||
color_resolution: k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_720P,
|
||||
depth_mode: k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED,
|
||||
camera_fps: 0,
|
||||
synchronized_images_only: false,
|
||||
depth_delay_off_color_usec: 0,
|
||||
wired_sync_mode: 0,
|
||||
subordinate_delay_off_master_usec: 0,
|
||||
disable_streaming_indicator: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Resolution {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Transformation {
|
||||
transformation: k4a_sys::k4a_transformation_t,
|
||||
pub color_resolution: Resolution,
|
||||
pub depth_resolution: Resolution,
|
||||
}
|
||||
|
||||
impl Transformation {
|
||||
/// Creates a transformation associated with a calibration
|
||||
pub fn from_calibration(calibration: &Calibration) -> Self {
|
||||
let transformation = unsafe {
|
||||
k4a_sys::k4a_transformation_create(&calibration.0)
|
||||
};
|
||||
Self {
|
||||
transformation,
|
||||
color_resolution: Resolution {
|
||||
width: calibration.0.color_camera_calibration.resolution_width,
|
||||
height: calibration.0.color_camera_calibration.resolution_height,
|
||||
},
|
||||
depth_resolution: Resolution {
|
||||
width: calibration.0.depth_camera_calibration.resolution_width,
|
||||
height: calibration.0.depth_camera_calibration.resolution_height,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying opaque handle *without* an additional refcount.
|
||||
/// Do not deallocate it.
|
||||
pub fn get_handle(&self) -> k4a_sys::k4a_transformation_t {
|
||||
self.transformation
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Transformation {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
k4a_sys::k4a_transformation_destroy(self.transformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod capture;
|
||||
pub mod sensor_control;
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::image_debug::depth_to_image;
|
||||
use image::DynamicImage;
|
||||
use kinect::{Capture, Device};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/** Self-locking holder of Capture objects. */
|
||||
pub struct CaptureProvider {
|
||||
capture: Arc<Mutex<Option<Capture>>>,
|
||||
}
|
||||
|
||||
impl CaptureProvider {
|
||||
pub fn new() -> Self {
|
||||
CaptureProvider {
|
||||
capture: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the latest capture, if available, through interior mutability.
|
||||
* This leaves the mutex holding None.
|
||||
*/
|
||||
pub fn get_capture(&self) -> Option<Capture> {
|
||||
self.capture.lock()
|
||||
.ok()
|
||||
.and_then(|mut lock| lock.take())
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume capture and replace whatever we currently hold.
|
||||
*/
|
||||
pub fn set_capture(&self, capture: Capture) {
|
||||
match self.capture.lock() {
|
||||
Ok(mut lock) => {
|
||||
*lock = Some(capture)
|
||||
},
|
||||
Err(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capture_thread(capture_provider: Arc<CaptureProvider>, device: Option<Device>, start_camera: bool) {
|
||||
let start_camera = device.is_none() || start_camera;
|
||||
|
||||
let device = match device {
|
||||
Some(device) => device,
|
||||
None => {
|
||||
let installed_devices = Device::get_installed_count();
|
||||
println!("Installed devices: {}", installed_devices);
|
||||
|
||||
let device = Device::open(0).unwrap();
|
||||
println!("Device: {:?}", device);
|
||||
device
|
||||
},
|
||||
};
|
||||
|
||||
let serial_number = device.get_serial_number().unwrap();
|
||||
println!("Device: {:?}", serial_number);
|
||||
|
||||
if start_camera {
|
||||
println!("Starting cameras...");
|
||||
device.start_cameras_default_config().unwrap();
|
||||
}
|
||||
|
||||
loop {
|
||||
let capture = device.get_capture(1000)
|
||||
.expect("Should be able to get frame capture.");
|
||||
|
||||
capture_provider.set_capture(capture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
/// A core color type
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct RgbaF32 {
|
||||
pub red: f32,
|
||||
pub green: f32,
|
||||
pub blue: f32,
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
impl RgbaF32 {
|
||||
/// Black (alpha 1.0)
|
||||
pub fn black() -> Self {
|
||||
Self {
|
||||
red: 0.0,
|
||||
green: 0.0,
|
||||
blue: 0.0,
|
||||
alpha: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgb(r: f32, g: f32, b: f32) -> Self {
|
||||
Self {
|
||||
red: r,
|
||||
green: g,
|
||||
blue: b,
|
||||
alpha: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gray(gray: f32) -> Self {
|
||||
Self {
|
||||
red: gray,
|
||||
green: gray,
|
||||
blue: gray,
|
||||
alpha: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::core_types::RgbaF32;
|
||||
|
||||
#[test]
|
||||
pub fn test_rgbaf32() {
|
||||
let black = RgbaF32::black();
|
||||
assert_eq!(black.red, 0.0f32);
|
||||
assert_eq!(black.green, 0.0f32);
|
||||
assert_eq!(black.blue, 0.0f32);
|
||||
assert_eq!(black.alpha, 1.0f32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_gray() {
|
||||
let gray = RgbaF32::gray(0.5);
|
||||
assert_eq!(gray.red, 0.5f32);
|
||||
assert_eq!(gray.green, 0.5f32);
|
||||
assert_eq!(gray.blue, 0.5f32);
|
||||
assert_eq!(gray.alpha, 1.0f32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod read_file_string_contents;
|
||||
pub mod write_to_file_from_byte_ptr;
|
||||
@@ -0,0 +1,10 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
pub fn read_file_string_contents(filename: &str) -> AnyhowResult<String> {
|
||||
let mut file = File::open(filename)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::ptr;
|
||||
|
||||
/// Raw memory copy into a file
|
||||
pub fn write_to_file_from_byte_ptr(filename: &str, byte_src: *const u8, size_bytes: usize) -> AnyhowResult<()> {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(filename)?;
|
||||
|
||||
let mut vec = vec![0u8; size_bytes];
|
||||
|
||||
unsafe {
|
||||
let vec_ptr = vec.as_mut_ptr();
|
||||
//ptr::write_bytes(vec_ptr, color_src, size_bytes);
|
||||
ptr::copy_nonoverlapping(byte_src, vec_ptr, size_bytes);
|
||||
}
|
||||
|
||||
file.write(&vec)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use kinect::Calibration;
|
||||
use crate::ProgramArgs;
|
||||
use crate::core_types::RgbaF32;
|
||||
use crate::gui::enhanced_window::EnhancedWindow;
|
||||
use crate::gui::mouse_camera_arcball::MouseCameraArcball;
|
||||
use crate::azure_kinect::capture::device_capturer::CaptureProvider;
|
||||
use crate::point_cloud::pixel_structs::BgraPixel;
|
||||
use crate::point_cloud::point_cloud_visualiser::{ColorizationStrategy, PointCloudVisualizer, PointCloudVisualizerError};
|
||||
use crate::point_cloud::viewer_image::ViewerImage;
|
||||
use crate::webcam::WebcamWriter;
|
||||
use gl;
|
||||
use imgui::*;
|
||||
use imgui::Image;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex, PoisonError, MutexGuard};
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
pub fn run(capture_provider: Arc<CaptureProvider>, program_args: ProgramArgs) -> AnyhowResult<()> {
|
||||
let mut webcam_writer = None;
|
||||
|
||||
if program_args.enable_webcam_output_writing {
|
||||
webcam_writer = Some(WebcamWriter::open_file("/dev/video0", 1280, 720, 3)
|
||||
.expect("should be able to create webcamwriter"));
|
||||
}
|
||||
|
||||
let sdl_arcball = Arc::new(Mutex::new(MouseCameraArcball::new(1280, 720)));
|
||||
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video = sdl_context.video().unwrap();
|
||||
|
||||
{
|
||||
let gl_attr = video.gl_attr();
|
||||
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
|
||||
gl_attr.set_context_version(3, 0);
|
||||
}
|
||||
|
||||
let window = video.window("rust-imgui-sdl2 demo", 3000, 2000)
|
||||
.position_centered()
|
||||
.resizable()
|
||||
.opengl()
|
||||
.allow_highdpi()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let _gl_context = window.gl_create_context().expect("Couldn't create GL context");
|
||||
gl::load_with(|s| video.gl_get_proc_address(s) as _);
|
||||
|
||||
//enable_opengl_debugging();
|
||||
|
||||
let mut imgui = imgui::Context::create();
|
||||
imgui.set_ini_filename(None);
|
||||
imgui.io_mut().config_windows_move_from_title_bar_only = true;
|
||||
|
||||
/*let gl_texture_n64 = load_texture("n64logo.png");
|
||||
let imgui_texture_n64 = TextureId::from(gl_texture_n64 as usize);
|
||||
let mut gl_texture_snes = load_texture("sneslogo.png");
|
||||
let mut imgui_texture_snes = TextureId::from(gl_texture_snes as usize);*/
|
||||
|
||||
let colorization_strategy = ColorizationStrategy::Color;
|
||||
|
||||
let clear_color = RgbaF32::gray(0.5);
|
||||
|
||||
let mut visualizer = PointCloudVisualizer::new(
|
||||
capture_provider.get_num_cameras(),
|
||||
capture_provider.get_calibrations(),
|
||||
true,
|
||||
colorization_strategy,
|
||||
clear_color,
|
||||
sdl_arcball.clone(),
|
||||
);
|
||||
|
||||
//rebinder.restore();
|
||||
|
||||
let texture = ViewerImage::create(
|
||||
// TODO: The following commented-out values work and look great,
|
||||
// but now we're doing suboptimal webcam stuff:
|
||||
//1280,
|
||||
//1152,
|
||||
1280,
|
||||
720,
|
||||
).expect("ViewerImage texture creation should work");
|
||||
|
||||
let mut imgui_visualizer_xyz_texture_0 : Option<TextureId> = None;
|
||||
let mut imgui_visualizer_xyz_texture_1 : Option<TextureId> = None;
|
||||
|
||||
let imgui_point_cloud_convert_xy_table= TextureId::from(visualizer.point_cloud_converters.get(0).unwrap().xy_table_texture.id() as usize);
|
||||
|
||||
let imgui_kinect_final_output = TextureId::from(texture.texture_id() as usize);
|
||||
|
||||
let mut imgui_sdl2 = imgui_sdl2::ImguiSdl2::new(&mut imgui, &window);
|
||||
|
||||
let renderer = imgui_opengl_renderer::Renderer::new(&mut imgui, |s| video.gl_get_proc_address(s) as _);
|
||||
|
||||
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||
|
||||
let mut last_frame = Instant::now();
|
||||
let mut last_change = Instant::now();
|
||||
|
||||
let mut camera_index = 0;
|
||||
|
||||
visualizer.setup_rendering()?;
|
||||
|
||||
'running: loop {
|
||||
use sdl2::event::Event;
|
||||
use sdl2::keyboard::Keycode;
|
||||
|
||||
for event in event_pump.poll_iter() {
|
||||
imgui_sdl2.handle_event(&mut imgui, &event);
|
||||
|
||||
// TODO: This is how Imgui appears to tell SDL to ignore certain events.
|
||||
if imgui_sdl2.ignore_event(&event) { continue; }
|
||||
|
||||
match event {
|
||||
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
|
||||
break 'running
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
/*// Also send to the arcball to update the view matrix.
|
||||
match sdl_arcball.lock() {
|
||||
Ok(mut arcball) => arcball.process_event(&event),
|
||||
Err(_) => {},
|
||||
}*/
|
||||
}
|
||||
|
||||
imgui_sdl2.prepare_frame(imgui.io_mut(), &window, &event_pump.mouse_state());
|
||||
|
||||
let now = Instant::now();
|
||||
let delta = now - last_frame;
|
||||
let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0;
|
||||
last_frame = now;
|
||||
imgui.io_mut().delta_time = delta_s;
|
||||
|
||||
let ui = imgui.frame();
|
||||
|
||||
//ui.show_demo_window(&mut true);
|
||||
|
||||
const window_width : f32 = 1290.0;
|
||||
const window_height : f32 = 760.0;
|
||||
|
||||
if imgui_visualizer_xyz_texture_0.is_none() {
|
||||
// TODO: TEMPORARY MESS TO SUPPORT MULTI-CAMERA
|
||||
if visualizer.xyz_textures.get(0).unwrap().id() != 0 {
|
||||
imgui_visualizer_xyz_texture_0 = Some(TextureId::from(visualizer.xyz_textures.get(0).unwrap().id() as usize));
|
||||
}
|
||||
}
|
||||
|
||||
if imgui_visualizer_xyz_texture_1.is_none() {
|
||||
// TODO: TEMPORARY MESS TO SUPPORT MULTI-CAMERA
|
||||
if visualizer.xyz_textures.get(1).unwrap().id() != 0 {
|
||||
imgui_visualizer_xyz_texture_1 = Some(TextureId::from(visualizer.xyz_textures.get(1).unwrap().id() as usize));
|
||||
}
|
||||
}
|
||||
|
||||
Window::new(im_str!("Visualizer XYZ Texture (0)"))
|
||||
.scrollable(true)
|
||||
.size([window_width, window_height], Condition::FirstUseEver)
|
||||
.position([0.0, 0.0], Condition::FirstUseEver)
|
||||
.build(&ui, || {
|
||||
match imgui_visualizer_xyz_texture_0.as_ref() {
|
||||
None => {},
|
||||
Some(xyz_texture) => {
|
||||
Image::new(xyz_texture.clone(), [1280.0, 720.0]).build(&ui);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Window::new(im_str!("Visualizer XYZ Texture (1)"))
|
||||
.size([window_width, window_height], Condition::FirstUseEver)
|
||||
.position([window_width + 50.0, 0.0], Condition::FirstUseEver)
|
||||
.movable(false)
|
||||
.build(&ui, || {
|
||||
match imgui_visualizer_xyz_texture_1.as_ref() {
|
||||
None => {},
|
||||
Some(xyz_texture) => {
|
||||
Image::new(xyz_texture.clone(), [1280.0, 720.0]).build(&ui);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Window::new(im_str!("Point Cloud Converter XY Table"))
|
||||
.size([window_width, window_height], Condition::FirstUseEver)
|
||||
.position([0.0, window_height + 50.0], Condition::FirstUseEver)
|
||||
.build(&ui, || {
|
||||
Image::new(imgui_point_cloud_convert_xy_table, [1280.0, 720.0]).build(&ui);
|
||||
});
|
||||
|
||||
// NB: This is larger. The texture is 1280x1152.
|
||||
EnhancedWindow::new(&im_str!("Final Output: {:?}", colorization_strategy))
|
||||
.size([window_width, window_height + 435.0], Condition::FirstUseEver)
|
||||
.position([window_width + 50.0, window_height + 50.0], Condition::FirstUseEver)
|
||||
.mouse_inputs(true)
|
||||
.build(&ui, |window| {
|
||||
|
||||
if let Some(mouse_state) = window.get_window_bounded_mouse_state() {
|
||||
match sdl_arcball.lock() {
|
||||
Ok(mut arcball) => arcball.process_mouse_state(mouse_state),
|
||||
Err(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
Image::new(imgui_kinect_final_output, [1280.0, 720.0]).build(&ui);
|
||||
});
|
||||
|
||||
unsafe {
|
||||
gl::ClearColor(0.2, 0.2, 0.2, 1.0);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
imgui_sdl2.prepare_render(&ui, &window);
|
||||
renderer.render(ui);
|
||||
|
||||
window.gl_swap_window();
|
||||
|
||||
if let Some(mut captures) = capture_provider.get_captures() {
|
||||
if program_args.save_captures {
|
||||
// Only save the last frame (for now)
|
||||
for (i, capture) in captures.iter().enumerate() {
|
||||
let suffix = format!("{}", i);
|
||||
capture.debug_save_files(Path::new("output/"), &suffix).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
visualizer.update_texture_id(texture.texture_id(), captures)
|
||||
.map(|_| {})
|
||||
.map_err(|err| {
|
||||
match err {
|
||||
PointCloudVisualizerError::MissingDepthImage => { println!("Missing depth image"); },
|
||||
PointCloudVisualizerError::MissingColorImage => { println!("Missing color image"); },
|
||||
_ => {
|
||||
unreachable!("Error: {:?}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
match sdl_arcball.lock() {
|
||||
Ok(mut arcball) => {
|
||||
arcball.animate();
|
||||
},
|
||||
Err(_) => {},
|
||||
}
|
||||
|
||||
if let Some(mut webcam) = webcam_writer.as_mut() {
|
||||
webcam.write_current_frame_to_file(texture.texture_id())
|
||||
.expect("should write");
|
||||
}
|
||||
|
||||
let change_delta = last_frame - last_change;
|
||||
if change_delta > Duration::from_millis(5_000) {
|
||||
camera_index = (camera_index + 1) % capture_provider.get_num_cameras();
|
||||
last_change = Instant::now();
|
||||
}
|
||||
|
||||
/*if change_delta > Duration::from_millis(5000) {
|
||||
colorization_strategy = match colorization_strategy {
|
||||
ColorizationStrategy::Simple => ColorizationStrategy::Shaded, // NB: We won't do simple.
|
||||
ColorizationStrategy::Shaded => ColorizationStrategy::Color,
|
||||
ColorizationStrategy::Color => ColorizationStrategy::Shaded,
|
||||
};
|
||||
|
||||
println!("Changing colorization strategy: {:?}", colorization_strategy);
|
||||
|
||||
visualizer.set_colorization_strategy(colorization_strategy);
|
||||
last_change = Instant::now();
|
||||
}*/
|
||||
|
||||
//rebinder.restore();
|
||||
|
||||
std::thread::sleep(::std::time::Duration::new(0, 1_000_000_000u32 / 60));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/// A wrapper around `Imgui::Window` that adds window-scoped devices.
|
||||
pub struct EnhancedWindow <'a> {
|
||||
pub window: imgui::Window<'a>,
|
||||
}
|
||||
|
||||
impl <'a> EnhancedWindow <'a> {
|
||||
pub fn new(name: &'a imgui::ImStr) -> Self {
|
||||
let mut window = imgui::Window::new(name)
|
||||
.scrollable(true);
|
||||
|
||||
Self {
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables/disables catching mouse input.
|
||||
///
|
||||
/// Enabled by default.
|
||||
/// Note: Hovering test will pass through when disabled
|
||||
#[inline]
|
||||
pub fn mouse_inputs(mut self, value: bool) -> Self {
|
||||
// NB: Builder API consumes window, so we have to move it.
|
||||
self.window = self.window.mouse_inputs(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the window position, which is applied based on the given condition value
|
||||
#[inline]
|
||||
pub fn position(mut self, position: [f32; 2], condition: imgui::Condition) -> Self {
|
||||
// NB: Builder API consumes window, so we have to move it.
|
||||
self.window = self.window.position(position, condition);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the window size, which is applied based on the given condition value
|
||||
#[inline]
|
||||
pub fn size(mut self, size: [f32; 2], condition: imgui::Condition) -> Self {
|
||||
// NB: Builder API consumes window, so we have to move it.
|
||||
self.window = self.window.size(size, condition);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a window and runs a closure to construct the contents.
|
||||
///
|
||||
/// Note: the closure is not called if no window content is visible (e.g. window is collapsed
|
||||
/// or fully clipped).
|
||||
pub fn build<F>(self, ui: &imgui::Ui, f: F)
|
||||
where F: FnOnce(WindowScopedUi)
|
||||
{
|
||||
if let Some(window) = self.begin(ui) {
|
||||
let win_helper = WindowScopedUi::new(&ui);
|
||||
f(win_helper);
|
||||
window.end(ui);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a window and starts appending to it.
|
||||
///
|
||||
/// Returns `Some(WindowToken)` if the window is visible. After content has been
|
||||
/// rendered, the token must be ended by calling `.end()`.
|
||||
///
|
||||
/// Returns `None` if the window is not visible and no content should be rendered.
|
||||
#[must_use]
|
||||
pub fn begin(self, ui: &imgui::Ui) -> Option<imgui::WindowToken> {
|
||||
self.window.begin(ui)
|
||||
}
|
||||
}
|
||||
|
||||
/// State for a mouse mapped to the dimensions of an `Imgui::Window`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompleteMouseState {
|
||||
// Cursor position
|
||||
pub cursor_x: f32,
|
||||
pub cursor_y: f32,
|
||||
// Scroll wheel velocity (typically just '0', '1', or '-1').
|
||||
pub scroll_wheel: f32,
|
||||
pub scroll_wheel_h: f32,
|
||||
// Buttons
|
||||
pub left_button_down: bool,
|
||||
pub right_button_down: bool,
|
||||
pub middle_button_down: bool,
|
||||
pub extra_button_1_down: bool,
|
||||
pub extra_button_2_down: bool,
|
||||
}
|
||||
|
||||
/// Exposes some reusable calculations on top of `imgui::Ui` in the context of window usage.
|
||||
pub struct WindowScopedUi<'a> {
|
||||
ui: &'a imgui::Ui<'a>,
|
||||
}
|
||||
|
||||
impl <'a> WindowScopedUi<'a> {
|
||||
pub fn new(ui: &'a imgui::Ui) -> Self {
|
||||
Self {
|
||||
ui
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the current window is hovered
|
||||
#[inline]
|
||||
pub fn is_window_hovered(&self) -> bool {
|
||||
self.ui.is_window_hovered()
|
||||
}
|
||||
|
||||
/// Return the cursor position within the window.
|
||||
/// None is returned if the window isn't hovered, or the mouse is logically not within the drawn
|
||||
/// texture area.
|
||||
pub fn get_cursor(&self) -> Option<[f32; 2]> {
|
||||
if !self.is_window_hovered() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Global mouse coordinate
|
||||
let mouse_pos = self.ui.io().mouse_pos;
|
||||
// Anchor point of the window's top left corner
|
||||
let window_pos = self.ui.window_pos();
|
||||
// Start of textured data (padding offset created by title and window padding).
|
||||
let cursor_pos = self.ui.cursor_pos();
|
||||
|
||||
let x = mouse_pos[0] - window_pos[0] - cursor_pos[0];
|
||||
let y = mouse_pos[1] - window_pos[1] - cursor_pos[1];
|
||||
|
||||
if x.is_sign_negative() || y.is_sign_negative() {
|
||||
return None; // Outside of bounds (eg. cursor within title area)
|
||||
}
|
||||
|
||||
Some([x, y])
|
||||
}
|
||||
|
||||
/// Return the mouse scroll state. If the mouse is not hovering our window, None is returned.
|
||||
pub fn get_mouse_wheel(&self) -> Option<f32> {
|
||||
if self.get_cursor().is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(self.ui.io().mouse_wheel)
|
||||
}
|
||||
|
||||
/// Mouse buttons: 0=left, 1=right, 2=middle + extras
|
||||
pub fn get_mouse_down(&self) -> Option<[bool; 5]> {
|
||||
if self.get_cursor().is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(self.ui.io().mouse_down)
|
||||
}
|
||||
|
||||
/// Get the current mouse state
|
||||
/// If the cursor isn't within the window, None is returned.
|
||||
pub fn get_window_bounded_mouse_state(&self) -> Option<CompleteMouseState> {
|
||||
self.get_cursor()
|
||||
.map(|cursor| {
|
||||
let io = self.ui.io();
|
||||
let mouse = io.mouse_down;
|
||||
|
||||
CompleteMouseState {
|
||||
cursor_x: cursor[0],
|
||||
cursor_y: cursor[1],
|
||||
scroll_wheel: io.mouse_wheel,
|
||||
scroll_wheel_h: io.mouse_wheel_h,
|
||||
left_button_down: mouse[0],
|
||||
right_button_down: mouse[1],
|
||||
middle_button_down: mouse[2],
|
||||
extra_button_1_down: mouse[3],
|
||||
extra_button_2_down: mouse[4],
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
//! This module will contain all the Imgui, SDL, mouse controls, etc. code
|
||||
pub mod enhanced_window;
|
||||
pub mod mouse_camera_arcball;
|
||||
@@ -0,0 +1,214 @@
|
||||
use arcball::ArcballCamera;
|
||||
use cgmath::Vector3;
|
||||
use cgmath::{Matrix4, Vector2};
|
||||
use crate::gui::enhanced_window::CompleteMouseState;
|
||||
use sdl2::event::Event;
|
||||
use sdl2::mouse::MouseButton;
|
||||
|
||||
const MIN_ZOOM : f32 = 1.0;
|
||||
const MAX_ZOOM : f32 = 120.0;
|
||||
const DEFAULT_ZOOM: f32 = 65.0;
|
||||
|
||||
// TODO: Enable camera resizing (or change of texture to differently sized texture)
|
||||
/// Wrap a camera arcball for use with SDL
|
||||
pub struct MouseCameraArcball {
|
||||
// Arcball camera state
|
||||
arcball: ArcballCamera<f32>,
|
||||
|
||||
// Dimensions of the window
|
||||
window_width: u32,
|
||||
window_height: u32,
|
||||
|
||||
// Last mouse state
|
||||
left_mouse_active: bool,
|
||||
right_mouse_active: bool,
|
||||
last_x: f32,
|
||||
last_y: f32,
|
||||
last_zoom: f32,
|
||||
|
||||
animation_counter: f32,
|
||||
}
|
||||
|
||||
impl MouseCameraArcball {
|
||||
/// CTOR
|
||||
pub fn new(window_width: u32, window_height: u32) -> Self {
|
||||
let center = Vector3::new(0.0, 0.0, 0.0);
|
||||
let zoom_speed = 1.0;
|
||||
let screen = [window_width as f32, window_height as f32];
|
||||
let mut arcball = ArcballCamera::new(center, zoom_speed, screen);
|
||||
|
||||
// TODO: This should be cleaned up to use pure math rather than stupid experimentally determined fixes
|
||||
arcball.zoom(-8.0, 0.16);
|
||||
|
||||
let mut prev = Vector2::new(window_width as f32, (window_height/2) as f32);
|
||||
|
||||
for i in 0 .. window_width / 2 {
|
||||
let pos = Vector2::new(prev.x - 1.0, (window_height/2) as f32);
|
||||
arcball.rotate(prev, pos);
|
||||
prev = pos.clone();
|
||||
}
|
||||
|
||||
for i in 0..600 {
|
||||
let delta = Vector2::new(0.0, 1.0);
|
||||
arcball.pan(delta, 0.16);
|
||||
}
|
||||
|
||||
Self {
|
||||
arcball,
|
||||
window_width,
|
||||
window_height,
|
||||
left_mouse_active: false,
|
||||
right_mouse_active: false,
|
||||
last_x: 0.0,
|
||||
last_y: 0.0,
|
||||
last_zoom: DEFAULT_ZOOM,
|
||||
|
||||
animation_counter: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an SDL event.
|
||||
/// This is a bad idea in a multi-window Imgui setup since the SDL coordinates and events take up
|
||||
/// the entire process window and may be sequestered on a per-window level. That said, this is
|
||||
/// reasonable for single-window apps.
|
||||
pub fn process_event(&mut self, event: &Event) {
|
||||
match event {
|
||||
Event::MouseWheel { timestamp: _, window_id: _, which: _, x: _, y, direction: _ } => {
|
||||
//println!("Mouse wheel event.");
|
||||
let y = *y as f32;
|
||||
self.mouse_wheel(y);
|
||||
},
|
||||
Event::MouseButtonDown { timestamp: _, window_id: _, which: _, mouse_btn, clicks: _, x: _, y: _ } => {
|
||||
//println!("Mouse button down event.");
|
||||
self.mouse_button_press(mouse_btn, true);
|
||||
},
|
||||
Event::MouseButtonUp { timestamp: _, window_id: _, which: _, mouse_btn, clicks: _, x: _, y: _ } => {
|
||||
//println!("Mouse button up event.");
|
||||
self.mouse_button_press(mouse_btn, false);
|
||||
},
|
||||
Event::MouseMotion { timestamp: _, window_id: _, which: _, mousestate: _, x, y, xrel: _, yrel: _ } => {
|
||||
let x = *x as f32;
|
||||
let y = *y as f32;
|
||||
self.mouse_motion(x, y);
|
||||
self.last_x = x;
|
||||
self.last_y = y;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Process Imgui window-scoped mouse state.
|
||||
/// Since we don't process individual events, we set all of the internal state at once.
|
||||
pub fn process_mouse_state(&mut self, mouse_state: CompleteMouseState) {
|
||||
// TODO: Cleanup
|
||||
self.right_mouse_active = mouse_state.right_button_down;
|
||||
self.left_mouse_active = mouse_state.left_button_down;
|
||||
self.mouse_motion(mouse_state.cursor_x, mouse_state.cursor_y);
|
||||
self.mouse_wheel(mouse_state.scroll_wheel);
|
||||
self.last_x = mouse_state.cursor_x;
|
||||
self.last_y = mouse_state.cursor_y;
|
||||
}
|
||||
|
||||
/// Get the view matrix
|
||||
pub fn get_view_matrix(&self) -> Matrix4<f32> {
|
||||
self.arcball.get_mat4()
|
||||
}
|
||||
|
||||
/// Get the perspective matrix
|
||||
pub fn get_perspective_matrix(&self) -> Matrix4<f32> {
|
||||
let zoom = self.last_zoom;
|
||||
let y_fov = zoom.to_radians();
|
||||
let aspect = self.window_width as f32 / self.window_height as f32;
|
||||
let n = 0.1f32;
|
||||
let f = 1000.0f32;
|
||||
|
||||
// From linmath.h included in Microsoft's k4a open source code
|
||||
let a = 1.0f32 / (y_fov / 2.0).tan();
|
||||
|
||||
// NB: This is column-major, which is dumb.
|
||||
Matrix4::new(
|
||||
// Column 0
|
||||
a / aspect,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
|
||||
// Column 1
|
||||
0.0,
|
||||
a,
|
||||
0.0,
|
||||
0.0,
|
||||
|
||||
// Column 2
|
||||
0.0,
|
||||
0.0,
|
||||
-((f + n) / (f - n)),
|
||||
-((2.0 * f * n) / (f - n)),
|
||||
|
||||
// Column 3
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0, // NB: This is not zero
|
||||
0.0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn animate(&mut self) {
|
||||
let x = self.animation_counter.sin();
|
||||
let y = self.animation_counter.cos();
|
||||
self.animation_counter.sin();
|
||||
let mouse_delta = Vector2::new(x * 4.0, y * 4.0);
|
||||
self.arcball.pan(mouse_delta, 0.16);
|
||||
|
||||
|
||||
let x = self.animation_counter.sin();
|
||||
|
||||
if x > 0.0 {
|
||||
let y = 1.0;
|
||||
let prev = Vector2::new(0.0, 0.0);
|
||||
let cur = Vector2::new(y, y);
|
||||
self.arcball.rotate(prev, cur);
|
||||
} else {
|
||||
let y = 1.0;
|
||||
let prev = Vector2::new(y, y);
|
||||
let cur = Vector2::new(0.0, 0.0);
|
||||
self.arcball.rotate(prev, cur);
|
||||
};
|
||||
|
||||
|
||||
self.animation_counter += 0.05;
|
||||
}
|
||||
|
||||
fn mouse_wheel(&mut self, y: f32) {
|
||||
self.arcball.zoom(y, 0.16);
|
||||
|
||||
// NB: 'y' is a scroll amount, typically 1 or -1.
|
||||
let zoom = (self.last_zoom + y)
|
||||
.max(MIN_ZOOM)
|
||||
.min(MAX_ZOOM);
|
||||
|
||||
self.last_zoom = zoom;
|
||||
}
|
||||
|
||||
fn mouse_button_press(&mut self, mouse_button: &MouseButton, down: bool) {
|
||||
match mouse_button {
|
||||
MouseButton::Left => self.left_mouse_active = down,
|
||||
MouseButton::Right => self.right_mouse_active = down,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_motion(&mut self, x: f32, y: f32) {
|
||||
if self.left_mouse_active {
|
||||
let previous = Vector2::new(self.last_x, self.last_y);
|
||||
let current = Vector2::new(x, y);
|
||||
self.arcball.rotate(previous, current);
|
||||
} else if self.right_mouse_active {
|
||||
let mouse_delta = Vector2::new(
|
||||
x - self.last_x,
|
||||
self.last_y - y, // Invert scrolling
|
||||
);
|
||||
self.arcball.pan(mouse_delta, 0.16);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//! Format conversion
|
||||
//! k4a frames, image-rs, opencv, OpenGL textures, etc.
|
||||
|
||||
use crate::point_cloud::pixel_structs::{BgraPixel, DepthPixel};
|
||||
use glium::texture::RawImage2d;
|
||||
use image::DynamicImage;
|
||||
use image::GenericImage;
|
||||
use image::ImageBuffer;
|
||||
use image::ImageError;
|
||||
use image::Rgba;
|
||||
use image::RgbaImage;
|
||||
use image::error::{ImageFormatHint, UnsupportedError};
|
||||
use image::flat::{FlatSamples, SampleLayout};
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::Image;
|
||||
use kinect::ImageFormat;
|
||||
use std::mem::size_of;
|
||||
use std::slice;
|
||||
|
||||
// https://docs.rs/image/0.23.2/image/flat/index.html
|
||||
pub fn depth_to_image(image: &Image) -> Result<DynamicImage, ImageError> {
|
||||
let len = image.get_size();
|
||||
let samples = unsafe { slice::from_raw_parts(image.get_buffer(), len) };
|
||||
|
||||
let layout = SampleLayout::row_major_packed(
|
||||
4,
|
||||
image.get_width_pixels() as u32,
|
||||
image.get_height_pixels() as u32);
|
||||
|
||||
let buffer = FlatSamples {
|
||||
samples,
|
||||
layout,
|
||||
color_hint: None,
|
||||
};
|
||||
|
||||
let view = buffer.as_view::<Rgba<u8>>()
|
||||
.expect("view should work");
|
||||
|
||||
let img: RgbaImage = ImageBuffer::new(
|
||||
image.get_width_pixels() as u32,
|
||||
image.get_height_pixels() as u32);
|
||||
let mut img = DynamicImage::ImageRgba8(img);
|
||||
|
||||
img.copy_from(&view, 0, 0).expect("Should be able to copy");
|
||||
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
// Not efficient, but good for debugging
|
||||
pub fn k4a_image_to_rust_image_for_debug(image: &Image) -> Result<RgbaImage, ImageError> {
|
||||
let width = image.get_width_pixels() as u32;
|
||||
let height= image.get_height_pixels() as u32;
|
||||
let image_buffer = image.get_buffer();
|
||||
let image_format = image.get_format();
|
||||
|
||||
println!("k4a_image_to_rust: {}x{} (format={:?}", width, height, image_format);
|
||||
|
||||
let output_image = DynamicImage::new_rgba16(width, height);
|
||||
let mut rgba_image = output_image.to_rgba();
|
||||
let mut offset = 0;
|
||||
|
||||
match image_format {
|
||||
ImageFormat::Depth16 => {
|
||||
let typed_buffer = image_buffer as *const DepthPixel;
|
||||
for y in 0 .. height {
|
||||
for x in 0 .. width {
|
||||
let pixel = unsafe { *typed_buffer.offset(offset) };
|
||||
let scaled_pixel = pixel as f32 / std::u16::MAX as f32;
|
||||
let scaled_pixel = (scaled_pixel * std::u8::MAX as f32) as u8;
|
||||
rgba_image.put_pixel(x, y, Rgba([scaled_pixel, scaled_pixel, scaled_pixel, 255]));
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
ImageFormat::ColorBgra32 => {
|
||||
let typed_buffer = image_buffer as *const BgraPixel;
|
||||
for y in 0 .. height {
|
||||
for x in 0 .. width {
|
||||
let pixel = unsafe { &*typed_buffer.offset(offset) };
|
||||
// BGRA32: The fourth byte is the alpha channel and is unused in the Azure Kinect APIs.
|
||||
rgba_image.put_pixel(x, y, Rgba([pixel.red, pixel.green, pixel.blue, 255]));
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
ImageFormat::Custom => {
|
||||
// Stride is image_width * bytes_per_pixel, so we solve for bytes_per_pixel
|
||||
let stride = image.get_stride_bytes();
|
||||
let image_width = image.get_width_pixels();
|
||||
let bytes_per_pixel = stride / image_width;
|
||||
|
||||
// The only format we know how to represent is k4a_float2_t.
|
||||
let known_size = size_of::<k4a_sys::k4a_float2_t>();
|
||||
|
||||
if bytes_per_pixel != known_size {
|
||||
return Err(ImageError::Unsupported(UnsupportedError::from(ImageFormatHint::Unknown)))
|
||||
}
|
||||
|
||||
let typed_buffer = image_buffer as *const k4a_sys::k4a_float2_t;
|
||||
|
||||
let mut max_x = -10000.0;
|
||||
let mut max_y = -10000.0;
|
||||
let mut min_x= 10000.0;
|
||||
let mut min_y = 10000.0;
|
||||
|
||||
offset = 0;
|
||||
for _ in 0 .. height {
|
||||
for _ in 0 .. width {
|
||||
unsafe {
|
||||
let pixel = &*typed_buffer.offset(offset);
|
||||
let xp = pixel.xy.x;
|
||||
let yp = pixel.xy.y;
|
||||
if xp < min_x {
|
||||
min_x = xp;
|
||||
}
|
||||
if xp > max_x {
|
||||
max_x = xp;
|
||||
}
|
||||
if yp < min_y {
|
||||
min_y = yp;
|
||||
}
|
||||
if yp > max_y {
|
||||
max_y = yp;
|
||||
}
|
||||
}
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
//println!("Max x: {}", max_x);
|
||||
//println!("Max y: {}", max_y);
|
||||
//println!("Min x: {}", min_x);
|
||||
//println!("Min y: {}", min_y);
|
||||
|
||||
offset = 0;
|
||||
for y in 0 .. height {
|
||||
for x in 0 .. width {
|
||||
unsafe {
|
||||
let pixel = &*typed_buffer.offset(offset);
|
||||
let xp = pixel.xy.x;
|
||||
let yp = pixel.xy.y;
|
||||
|
||||
let scaled_x = (xp - min_x) / (max_x - min_x);
|
||||
let scaled_x = (255.0 * scaled_x) as u8;
|
||||
|
||||
let scaled_y = (yp - min_y) / (max_y - min_y);
|
||||
let scaled_y = (255.0 * scaled_y) as u8;
|
||||
|
||||
rgba_image.put_pixel(x, y, Rgba([scaled_x, 0, scaled_y, 255]));
|
||||
}
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => unimplemented!("conversion not implemented for: {:?}", image_format),
|
||||
};
|
||||
|
||||
return Ok(rgba_image)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||
use imgui::{ClipboardBackend, ImStr, ImString};
|
||||
|
||||
pub struct ClipboardSupport(ClipboardContext);
|
||||
|
||||
pub fn init() -> Option<ClipboardSupport> {
|
||||
ClipboardContext::new()
|
||||
.ok()
|
||||
.map(|ctx| ClipboardSupport(ctx))
|
||||
}
|
||||
|
||||
impl ClipboardBackend for ClipboardSupport {
|
||||
fn get(&mut self) -> Option<ImString> {
|
||||
self.0.get_contents().ok().map(|text| text.into())
|
||||
}
|
||||
fn set(&mut self, text: &ImStr) {
|
||||
let _ = self.0.set_contents(text.to_str().to_owned());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use glium::{Display, Surface};
|
||||
use glium::glutin;
|
||||
use glium::glutin::event::{Event, WindowEvent};
|
||||
use glium::glutin::event_loop::{ControlFlow, EventLoop};
|
||||
use glium::glutin::window::WindowBuilder;
|
||||
use imgui::{Context, FontConfig, FontSource, Ui};
|
||||
use imgui_glium_renderer::Renderer;
|
||||
use imgui_winit_support::{HiDpiMode, WinitPlatform};
|
||||
|
||||
mod clip;
|
||||
|
||||
pub struct System {
|
||||
pub event_loop: EventLoop<()>,
|
||||
pub display: glium::Display,
|
||||
pub imgui: Context,
|
||||
pub platform: WinitPlatform,
|
||||
pub renderer: Renderer,
|
||||
pub font_size: f32,
|
||||
}
|
||||
|
||||
pub fn init(title: &str) -> System {
|
||||
let title = match title.rfind('/') {
|
||||
Some(idx) => title.split_at(idx + 1).1,
|
||||
None => title,
|
||||
};
|
||||
let event_loop = EventLoop::new();
|
||||
let context = glutin::ContextBuilder::new().with_vsync(true);
|
||||
let builder = WindowBuilder::new()
|
||||
.with_title(title.to_owned())
|
||||
.with_inner_size(glutin::dpi::LogicalSize::new(1024f64, 768f64));
|
||||
let display =
|
||||
Display::new(builder, context, &event_loop).expect("Failed to initialize display");
|
||||
|
||||
let mut imgui = Context::create();
|
||||
imgui.set_ini_filename(None);
|
||||
|
||||
if let Some(backend) = clip::init() {
|
||||
imgui.set_clipboard_backend(Box::new(backend));
|
||||
} else {
|
||||
eprintln!("Failed to initialize clipboard");
|
||||
}
|
||||
|
||||
let mut platform = WinitPlatform::init(&mut imgui);
|
||||
{
|
||||
let gl_window = display.gl_window();
|
||||
let window = gl_window.window();
|
||||
platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded);
|
||||
}
|
||||
|
||||
// Load the OpenGL function pointers
|
||||
gl::load_with(|symbol| display.gl_window().get_proc_address(symbol));
|
||||
|
||||
let hidpi_factor = platform.hidpi_factor();
|
||||
let font_size = (13.0 * hidpi_factor) as f32;
|
||||
imgui.fonts().add_font(&[
|
||||
FontSource::DefaultFontData {
|
||||
config: Some(FontConfig {
|
||||
size_pixels: font_size,
|
||||
..FontConfig::default()
|
||||
}),
|
||||
},
|
||||
/*FontSource::TtfData {
|
||||
data: include_bytes!("../../../resources/mplus-1p-regular.ttf"),
|
||||
size_pixels: font_size,
|
||||
config: Some(FontConfig {
|
||||
rasterizer_multiply: 1.75,
|
||||
glyph_ranges: FontGlyphRanges::japanese(),
|
||||
..FontConfig::default()
|
||||
}),
|
||||
},*/
|
||||
]);
|
||||
|
||||
imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32;
|
||||
|
||||
let renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer");
|
||||
|
||||
System {
|
||||
event_loop,
|
||||
display,
|
||||
imgui,
|
||||
platform,
|
||||
renderer,
|
||||
font_size,
|
||||
}
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub fn main_loop<F: FnMut(&mut bool, &mut Ui) + 'static>(self, mut run_ui: F) {
|
||||
let System {
|
||||
event_loop,
|
||||
display,
|
||||
mut imgui,
|
||||
mut platform,
|
||||
mut renderer,
|
||||
..
|
||||
} = self;
|
||||
let mut last_frame = Instant::now();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| match event {
|
||||
Event::NewEvents(_) => last_frame = imgui.io_mut().update_delta_time(last_frame),
|
||||
Event::MainEventsCleared => {
|
||||
let gl_window = display.gl_window();
|
||||
platform
|
||||
.prepare_frame(imgui.io_mut(), &gl_window.window())
|
||||
.expect("Failed to prepare frame");
|
||||
gl_window.window().request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
let mut ui = imgui.frame();
|
||||
|
||||
let mut run = true;
|
||||
run_ui(&mut run, &mut ui);
|
||||
if !run {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
|
||||
let gl_window = display.gl_window();
|
||||
let mut target = display.draw();
|
||||
target.clear_color_srgb(1.0, 1.0, 1.0, 1.0);
|
||||
platform.prepare_render(&ui, gl_window.window());
|
||||
let draw_data = ui.render();
|
||||
renderer
|
||||
.render(&mut target, draw_data)
|
||||
.expect("Rendering failed");
|
||||
target.finish().expect("Failed to swap buffers");
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
event => {
|
||||
let gl_window = display.gl_window();
|
||||
platform.handle_event(imgui.io_mut(), gl_window.window(), &event);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#![allow(warnings)]
|
||||
|
||||
#[macro_use] extern crate clap;
|
||||
#[macro_use] extern crate glium;
|
||||
#[macro_use] extern crate imgui;
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
extern crate anyhow;
|
||||
extern crate arcball;
|
||||
extern crate cgmath;
|
||||
extern crate clipboard;
|
||||
extern crate gl;
|
||||
extern crate glfw;
|
||||
extern crate glfw_sys;
|
||||
extern crate image;
|
||||
extern crate imgui_glium_renderer;
|
||||
extern crate imgui_opengl_renderer;
|
||||
extern crate imgui_sdl2;
|
||||
extern crate imgui_winit_support;
|
||||
extern crate libc;
|
||||
extern crate rand;
|
||||
extern crate sdl2;
|
||||
extern crate tobj;
|
||||
extern crate winit;
|
||||
extern crate gltf;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{thread, env};
|
||||
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use anyhow::bail;
|
||||
use kinect::{Device, Calibration};
|
||||
use azure_kinect::sensor_control::capture_thread;
|
||||
use azure_kinect::capture::multi_device_capturer::{MultiDeviceCapturer, start_capture_thread};
|
||||
use azure_kinect::capture::fake_device_capturer::FakeDeviceCaptureProvider;
|
||||
use azure_kinect::capture::device_capturer::CaptureProvider;
|
||||
use clap::{Clap};
|
||||
|
||||
pub mod assets;
|
||||
pub mod azure_kinect;
|
||||
pub mod core_types;
|
||||
pub mod files;
|
||||
pub mod graphics_imgui;
|
||||
pub mod gui;
|
||||
pub mod image_debug;
|
||||
pub mod imgui_support;
|
||||
pub mod opengl;
|
||||
pub mod point_cloud;
|
||||
pub mod webcam;
|
||||
|
||||
const ENV_RUST_LOG : &'static str = "RUST_LOG";
|
||||
const DEFAULT_RUST_LOG: &'static str = "debug,actix_web=info"; // NB: We don't use actix, but this is preserved for syntax ref
|
||||
|
||||
/// This is the serial number for the camera I have mounted to the utility wall.
|
||||
const PRIMARY_DEVICE_SERIAL : &'static str = "000513594512";
|
||||
|
||||
/// This is a second device
|
||||
const SECONDARY_DEVICE_SERIAL : &'static str = "000886694512";
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
struct Opts {
|
||||
#[clap(long, parse(try_from_str = true_or_false), default_value = "true")]
|
||||
pub enable_cameras: bool,
|
||||
#[clap(long, parse(try_from_str = true_or_false), default_value = "false")]
|
||||
pub enable_webcam: bool,
|
||||
#[clap(long, parse(try_from_str = true_or_false), default_value = "false")]
|
||||
pub save_captures: bool,
|
||||
}
|
||||
|
||||
fn true_or_false(s: &str) -> Result<bool, &'static str> {
|
||||
match s {
|
||||
"true" => Ok(true),
|
||||
"false" => Ok(false),
|
||||
_ => Err("expected `true` or `false`"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Arguments to pass through the program
|
||||
pub struct ProgramArgs {
|
||||
pub save_captures: bool,
|
||||
pub enable_webcam_output_writing: bool,
|
||||
}
|
||||
|
||||
pub fn main() -> AnyhowResult<()> {
|
||||
if env::var(ENV_RUST_LOG)
|
||||
.as_ref()
|
||||
.ok()
|
||||
.is_none()
|
||||
{
|
||||
println!("Setting default logging level to \"{}\", override with env var {}.",
|
||||
DEFAULT_RUST_LOG, ENV_RUST_LOG);
|
||||
env::set_var(ENV_RUST_LOG, DEFAULT_RUST_LOG);
|
||||
}
|
||||
|
||||
env_logger::init();
|
||||
|
||||
let opts = Opts::parse();
|
||||
|
||||
println!("opts: {:?}", opts);
|
||||
|
||||
// Validate args.
|
||||
if opts.save_captures && !opts.enable_cameras {
|
||||
bail!("Can't save captures without cameras enabled.");
|
||||
}
|
||||
|
||||
let program_args = ProgramArgs {
|
||||
enable_webcam_output_writing: opts.enable_webcam,
|
||||
save_captures: opts.save_captures,
|
||||
};
|
||||
|
||||
let capture_provider: Arc<dyn CaptureProvider> = if opts.enable_cameras {
|
||||
let multi_device = MultiDeviceCapturer::new(2, Some(1))
|
||||
.expect("multi-device create");
|
||||
|
||||
multi_device.start_cameras().expect("start cameras");
|
||||
|
||||
let capture_provider = multi_device.get_sync_capture_provider();
|
||||
|
||||
thread::spawn(move || start_capture_thread(multi_device));
|
||||
|
||||
capture_provider
|
||||
} else {
|
||||
Arc::new(FakeDeviceCaptureProvider::new().unwrap())
|
||||
};
|
||||
|
||||
//let calibration = capture_provider.get_calibration().clone();
|
||||
//calibration.debug_print();
|
||||
|
||||
graphics_imgui::run(capture_provider, program_args)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
pub mod zeromq;
|
||||
pub use anyhow::Result as AnyhowResult;
|
||||
use anyhow::anyhow;
|
||||
use crate::zeromq::xy_table::create_xy_table;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::{Device, DeviceConfiguration};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use crate::zeromq::cpu_calculate_point_cloud::calculate_point_cloud;
|
||||
|
||||
fn main() -> AnyhowResult<()> {
|
||||
let device = Device::open(0)?;
|
||||
|
||||
let mut config = DeviceConfiguration::init_disable_all();
|
||||
config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED;
|
||||
config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
|
||||
|
||||
let calibration = device.get_calibration(config.0.depth_mode, config.0.color_resolution)?;
|
||||
|
||||
let xy_table = create_xy_table(&calibration)?;
|
||||
|
||||
device.start_cameras(&config)?;
|
||||
|
||||
loop {
|
||||
println!("Get capture");
|
||||
|
||||
let capture = device.get_capture(500)?;
|
||||
|
||||
let depth_image = capture.get_depth_image()
|
||||
.ok_or(anyhow!("capture not present"))?;
|
||||
|
||||
let point_cloud = calculate_point_cloud(&depth_image, &xy_table)?;
|
||||
|
||||
println!("Number points: {}", point_cloud.point_count);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
//! Generate random points to send over ZeroMQ to a consumer
|
||||
|
||||
#[allow(unused_imports)]
|
||||
|
||||
mod zeromq;
|
||||
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{WriteBytesExt, LittleEndian, BigEndian};
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::{Device, DeviceConfiguration, Image};
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use zeromq::cpu_calculate_point_cloud::PointCloudResult;
|
||||
use zeromq::cpu_calculate_point_cloud::calculate_point_cloud2;
|
||||
use zeromq::cpu_calculate_point_cloud::calculate_point_cloud3;
|
||||
use zeromq::color::Color;
|
||||
use zeromq::cpu_transformation::DepthTransformer;
|
||||
use zeromq::point::Point;
|
||||
use zeromq::xy_table::create_xy_table;
|
||||
use zeromq::xy_table::create_xy_table_from_depth_calibration;
|
||||
use zeromq::xy_table::create_xy_table_from_color_calibration;
|
||||
use zmq::{Error, Socket, Context, DONTWAIT};
|
||||
|
||||
const SOCKET_ADDRESS : &'static str = "tcp://127.0.0.1:8888";
|
||||
|
||||
const DATA_LENGTH_COMMAND : u32 = 1; // Denotes the command that sends the data length
|
||||
const POINT_DATA_BEGIN_PAYLOAD_COMMAND : u32 = 2; // Denotes the command that sends the variable-length data
|
||||
const POINT_DATA_CONTINUE_PAYLOAD_COMMAND : u32 = 3; // Denotes the command that sends the variable-length data
|
||||
|
||||
/// The maximum number of points to send per ZeroMQ "packet".
|
||||
/// This is also defined in C++, so it needs to be adjusted in multiple places.
|
||||
const MAX_SEND_POINTS_PER_PACKET : usize = 3000;
|
||||
|
||||
enum MessagingState {
|
||||
Sending_DataLength,
|
||||
//Receiving_DataLengthAck,
|
||||
Sending_PointDataBegin,
|
||||
//Receiving_PointDataAck,
|
||||
Sending_PointDataContinue,
|
||||
GrabPointCloud,
|
||||
}
|
||||
|
||||
fn main() -> AnyhowResult<()> {
|
||||
let device = Device::open(0)?;
|
||||
|
||||
let mut config = DeviceConfiguration::init_disable_all();
|
||||
config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_15;
|
||||
//config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
|
||||
|
||||
// TODO: Pick correct binning + FOV.
|
||||
|
||||
// NB: Similar to NFOV_UNBINNED, but less dense (faster).
|
||||
// Not used by any program I'm aware of.
|
||||
//config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_2X2BINNED; // 320x228
|
||||
|
||||
// NB: NFOV_UNBINNED was used by original Rust experiment.
|
||||
// This appears to be much denser, and is a tighter angle. Slow on CPU.
|
||||
//config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED; // looks good w/ 720P
|
||||
|
||||
// NB: WFOV_2X2BINNED was used by the original 'cloudcam_zeromq'.
|
||||
// It's less dense, and wider. Much more performant. Fast on CPU.
|
||||
//config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED; // DISTORTED w/ 720P
|
||||
|
||||
// Not used by anything, AFAICT.
|
||||
// I got this working in Rust + Color Camera
|
||||
config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_UNBINNED; // 1024x1024 (looks good w/ 720P)
|
||||
|
||||
|
||||
config.0.color_format = k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_BGRA32;
|
||||
config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_720P; // 1280x721
|
||||
//config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_1080P; // 1920x1080 (this gets truncated.)
|
||||
//config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P; // 4K, what the original program did
|
||||
|
||||
let calibration = device.get_calibration(config.0.depth_mode, config.0.color_resolution)?;
|
||||
|
||||
//let xy_table = create_xy_table_from_depth_calibration(&calibration)?;
|
||||
let xy_table = create_xy_table_from_color_calibration(&calibration)?;
|
||||
|
||||
let transformer = DepthTransformer::new(&calibration);
|
||||
|
||||
device.start_cameras(&config)?;
|
||||
|
||||
// ===============================================================
|
||||
|
||||
let context = zmq::Context::new();
|
||||
|
||||
let mut socket = context.socket(zmq::PUSH).unwrap();
|
||||
//let mut socket = context.socket(zmq::REQ).unwrap();
|
||||
|
||||
//socket.bind(SOCKET_ADDRESS).unwrap();
|
||||
socket.connect(SOCKET_ADDRESS).unwrap();
|
||||
|
||||
let mut messaging_state = MessagingState::Sending_DataLength;
|
||||
let mut color = Color::get_time_based_color();
|
||||
|
||||
let mut points : Vec<Point> = Vec::new();
|
||||
|
||||
loop {
|
||||
let maybe_points = get_point_cloud(&device, &xy_table, color, &transformer);
|
||||
points = match maybe_points {
|
||||
Err(e) => {
|
||||
println!("Error: {:?}", e);
|
||||
thread::sleep(Duration::from_millis(5000));
|
||||
continue;
|
||||
},
|
||||
Ok(points) => points,
|
||||
};
|
||||
break;
|
||||
};
|
||||
|
||||
if !points.is_empty() {
|
||||
//print_pointcloud_maxima(&points);
|
||||
} else {
|
||||
println!("> No points?");
|
||||
}
|
||||
|
||||
let mut packet_number = 0;
|
||||
|
||||
loop {
|
||||
match messaging_state {
|
||||
MessagingState::Sending_DataLength => {
|
||||
//println!("Sending DATA LENGTH: {}", points.len());
|
||||
//let message = encode_data_length(&points);
|
||||
//send_message(&socket, &message)?;
|
||||
messaging_state = MessagingState::Sending_PointDataBegin;
|
||||
},
|
||||
MessagingState::Sending_PointDataBegin => {
|
||||
//println!("Begin Sending PCD (points: {})", points.len());
|
||||
let message = encode_point_data(&mut points, true);
|
||||
send_message(&socket, &message)?;
|
||||
|
||||
if points.is_empty() {
|
||||
messaging_state = MessagingState::GrabPointCloud;
|
||||
} else {
|
||||
messaging_state = MessagingState::Sending_PointDataContinue;
|
||||
}
|
||||
|
||||
},
|
||||
MessagingState::Sending_PointDataContinue => {
|
||||
// NB: Unfortunately the C++ program has difficulty with all points.
|
||||
if packet_number > 2000000 {
|
||||
println!("Packet elapsed");
|
||||
packet_number = 0;
|
||||
messaging_state = MessagingState::GrabPointCloud;
|
||||
continue;
|
||||
}
|
||||
|
||||
//println!("Continue Sending PCD (points: {})", points.len());
|
||||
let message = encode_point_data(&mut points, false);
|
||||
send_message(&socket, &message)?;
|
||||
|
||||
if points.is_empty() {
|
||||
messaging_state = MessagingState::GrabPointCloud;
|
||||
} else {
|
||||
messaging_state = MessagingState::Sending_PointDataContinue;
|
||||
}
|
||||
|
||||
packet_number += 1;
|
||||
},
|
||||
MessagingState::GrabPointCloud => {
|
||||
//println!("Grabbing another frame...");
|
||||
color = Color::get_time_based_color();
|
||||
points = get_point_cloud(&device, &xy_table, color, &transformer)?;
|
||||
|
||||
if !points.is_empty() {
|
||||
//print_pointcloud_maxima(&points);
|
||||
} else {
|
||||
println!("No points?");
|
||||
}
|
||||
messaging_state = MessagingState::Sending_DataLength;
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_point_cloud(device: &Device, xy_table: &Image, color: Color, transformer: &DepthTransformer)
|
||||
-> AnyhowResult<Vec<Point>>
|
||||
{
|
||||
let capture = device.get_capture(5000)?;
|
||||
|
||||
let depth_image = capture.get_depth_image()
|
||||
.ok_or(anyhow!("depth image not present"))?;
|
||||
|
||||
let color_image = capture.get_color_image()
|
||||
.ok_or(anyhow!("color image not present"))?;
|
||||
|
||||
|
||||
let depth_image2 = transformer.transform(&depth_image)?;
|
||||
|
||||
let mut points =
|
||||
calculate_point_cloud3(
|
||||
&depth_image2,
|
||||
&xy_table,
|
||||
//color,
|
||||
&color_image
|
||||
)?;
|
||||
|
||||
//println!("Points: {}", points.len());
|
||||
|
||||
Ok(points)
|
||||
}
|
||||
|
||||
/// Returns the fixed size data length command.
|
||||
fn encode_data_length(points: &Vec<Point>) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(8);
|
||||
buf.write_u32::<LittleEndian>(DATA_LENGTH_COMMAND);
|
||||
buf.write_u32::<LittleEndian>(points.len() as u32);
|
||||
buf
|
||||
}
|
||||
|
||||
/// Returns a variable length point data payload.
|
||||
fn encode_point_data(points: &mut Vec<Point>, is_beginning: bool) -> Vec<u8> {
|
||||
let point_bytes = Point::size_bytes() * MAX_SEND_POINTS_PER_PACKET;
|
||||
|
||||
let mut buf = Vec::with_capacity(4 + point_bytes);
|
||||
|
||||
if is_beginning {
|
||||
//println!("begin");
|
||||
buf.write_u32::<LittleEndian>(POINT_DATA_BEGIN_PAYLOAD_COMMAND); // COMMAND #
|
||||
} else {
|
||||
//println!("continue");
|
||||
buf.write_u32::<LittleEndian>(POINT_DATA_CONTINUE_PAYLOAD_COMMAND); // COMMAND #
|
||||
}
|
||||
|
||||
let subset = if points.len() > MAX_SEND_POINTS_PER_PACKET {
|
||||
points.drain(0..MAX_SEND_POINTS_PER_PACKET).collect::<Vec<Point>>()
|
||||
} else {
|
||||
points.drain(0..points.len()).collect::<Vec<Point>>()
|
||||
};
|
||||
|
||||
buf.write_u32::<LittleEndian>(subset.len() as u32); // LENGTH
|
||||
|
||||
for point in subset {
|
||||
let bytes = point.to_bytes();
|
||||
buf.write_all(&bytes);
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
/// Send data over the socket
|
||||
fn send_message(socket: &Socket, data_bytes: &Vec<u8>) -> AnyhowResult<()> {
|
||||
socket.send(&data_bytes, 0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receive ack from server
|
||||
fn receive_ack(socket: &Socket) -> AnyhowResult<()> {
|
||||
let _result = socket.recv_bytes(DONTWAIT)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reconnect_socket(context: &Context, socket: Socket, address: &str) -> Socket {
|
||||
//println!("[reconnect] Creating new socket...");
|
||||
|
||||
let mut socket = match context.socket(zmq::REQ) {
|
||||
Ok(s) => {
|
||||
//println!("New socket created.");
|
||||
s
|
||||
},
|
||||
Err(e) => {
|
||||
//println!("Error creating new socket: {:?}", e);
|
||||
return socket;
|
||||
},
|
||||
};
|
||||
|
||||
//println!("Connecting new socket...");
|
||||
match socket.connect(address) {
|
||||
Ok(_) => {
|
||||
//println!("New socket connected.");
|
||||
},
|
||||
Err(err) => {
|
||||
//println!("Error connecting new socket: {:?}", err);
|
||||
},
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
|
||||
use gl;
|
||||
use gl::types::*;
|
||||
|
||||
pub fn compile_shader(src: &str, shader_type: GLenum) -> GLuint {
|
||||
let shader;
|
||||
unsafe {
|
||||
shader = gl::CreateShader(shader_type);
|
||||
// Attempt to compile the shader
|
||||
let c_str = CString::new(src.as_bytes()).unwrap();
|
||||
gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null());
|
||||
gl::CompileShader(shader);
|
||||
|
||||
// Get the compile status
|
||||
let mut status = gl::FALSE as GLint;
|
||||
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);
|
||||
|
||||
// Fail on error
|
||||
if status != (gl::TRUE as GLint) {
|
||||
let mut len = 0;
|
||||
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
|
||||
let mut buf = Vec::with_capacity(len as usize);
|
||||
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
|
||||
gl::GetShaderInfoLog(
|
||||
shader,
|
||||
len,
|
||||
ptr::null_mut(),
|
||||
buf.as_mut_ptr() as *mut GLchar,
|
||||
);
|
||||
panic!(
|
||||
"{}",
|
||||
str::from_utf8(&buf)
|
||||
.ok()
|
||||
.expect("ShaderInfoLog not valid utf8")
|
||||
);
|
||||
}
|
||||
}
|
||||
shader
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//! Add an OpenGL debugging callback.
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr::null;
|
||||
|
||||
use gl;
|
||||
use gl::types::*;
|
||||
|
||||
extern "system" fn glDebugOutput(
|
||||
_source: GLenum,
|
||||
_gltype: GLenum,
|
||||
id: GLuint,
|
||||
_severity: GLenum,
|
||||
_length: GLsizei,
|
||||
message: *const GLchar,
|
||||
_userParam: *mut c_void)
|
||||
{
|
||||
let message = unsafe { CStr::from_ptr(message) };
|
||||
//println!(">>> [GL DEBUG] id: {:?} message: {:?} source: {:?} type: {:?} severity: {:?}",
|
||||
// id, message, source, gltype, severity);
|
||||
println!(">>> [GL DEBUG] id: {:?} message: {:?}", id, message);
|
||||
}
|
||||
|
||||
pub fn enable_opengl_debugging() {
|
||||
unsafe {
|
||||
// Set debugging. This can be expensive, so we should be able to trigger it with a flag.
|
||||
gl::Enable(gl::DEBUG_OUTPUT);
|
||||
gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
gl::DebugMessageCallback(Some(glDebugOutput), null());
|
||||
// Opt into everything.
|
||||
gl::DebugMessageControl(gl::DONT_CARE, gl::DONT_CARE, gl::DONT_CARE, 0, null(), gl::TRUE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use gl::types::*;
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
|
||||
pub fn link_shader_program(program: GLuint, vertex_shader: GLuint, fragment_shader: GLuint) -> GLuint {
|
||||
unsafe {
|
||||
gl::AttachShader(program, vertex_shader);
|
||||
gl::AttachShader(program, fragment_shader);
|
||||
gl::LinkProgram(program);
|
||||
// Get the link status
|
||||
let mut status = gl::FALSE as GLint;
|
||||
gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);
|
||||
|
||||
// Fail on error
|
||||
if status != (gl::TRUE as GLint) {
|
||||
let mut len: GLint = 0;
|
||||
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
|
||||
let mut buf = Vec::with_capacity(len as usize);
|
||||
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
|
||||
gl::GetProgramInfoLog(
|
||||
program,
|
||||
len,
|
||||
ptr::null_mut(),
|
||||
buf.as_mut_ptr() as *mut GLchar,
|
||||
);
|
||||
panic!(
|
||||
"{}",
|
||||
str::from_utf8(&buf)
|
||||
.ok()
|
||||
.expect("ProgramInfoLog not valid utf8")
|
||||
);
|
||||
}
|
||||
program
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use gl::types::*;
|
||||
use gl;
|
||||
use image;
|
||||
use std::ffi::c_void;
|
||||
use std::path::Path;
|
||||
|
||||
/// Load a texture from a file.
|
||||
pub fn load_texture_from_file(filename: &str) -> GLuint {
|
||||
let mut texture_id = 0;
|
||||
|
||||
unsafe {
|
||||
gl::GenTextures(1, &mut texture_id);
|
||||
gl::BindTexture(gl::TEXTURE_2D, texture_id);
|
||||
|
||||
// Set up filtering params and other texture attributes
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
|
||||
}
|
||||
|
||||
let img = image::open(&Path::new(filename))
|
||||
.expect("failed to load")
|
||||
.to_rgba();
|
||||
|
||||
let width = img.dimensions().0 as i32;
|
||||
let height = img.dimensions().1 as i32;
|
||||
let data = img.into_raw();
|
||||
|
||||
println!("Loaded image: {}x{}", width, height);
|
||||
|
||||
unsafe {
|
||||
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
|
||||
gl::TexImage2D(
|
||||
gl::TEXTURE_2D,
|
||||
0,
|
||||
gl::RGBA as i32,
|
||||
//format as i32,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
gl::RGBA,
|
||||
//format,
|
||||
gl::UNSIGNED_BYTE,
|
||||
&data[0] as *const u8 as *const c_void,
|
||||
);
|
||||
|
||||
//gl::BindTexture(gl::TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
println!("Returning texture id: {}", texture_id);
|
||||
return texture_id;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//! Useful matrix functions
|
||||
|
||||
pub const fn translation_matrix_4x4_flat(x: f32, y: f32, z: f32) -> [f32; 16] {
|
||||
[
|
||||
1.0, 0.0, 0.0, x,
|
||||
0.0, 1.0, 0.0, y,
|
||||
0.0, 0.0, 1.0, z,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
}
|
||||
|
||||
pub const fn identity_matrix_4x4() -> [[f32; 4]; 4] {
|
||||
[
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
]
|
||||
}
|
||||
|
||||
pub const fn identity_matrix_4x4_flat() -> [f32; 16] {
|
||||
[
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
}
|
||||
|
||||
pub const fn zero_matrix_4x4_flat() -> [f32; 16] {
|
||||
return [
|
||||
0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
/// This is the view matrix k4aviewer starts with
|
||||
pub const fn initial_view_matrix_4x4() -> [[f32; 4]; 4] {
|
||||
[
|
||||
[-1.0, 0.0, 8.74228e-08, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[-8.74228e-08, 0.0, -1.0, 0.0],
|
||||
[2.62268e-07, 1.0, -5.0, 1.0],
|
||||
]
|
||||
}
|
||||
|
||||
/// This is the view matrix k4aviewer starts with
|
||||
pub const fn initial_view_matrix_4x4_flat() -> [f32; 16] {
|
||||
[
|
||||
-1.0, 0.0, 8.74228e-08, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
-8.74228e-08, 0.0, -1.0, 0.0,
|
||||
2.62268e-07, 1.0, -5.0, 1.0,
|
||||
]
|
||||
}
|
||||
|
||||
/// This is the projection matrix k4aviewer starts with
|
||||
pub const fn initial_projection_matrix_4x4() -> [[f32; 4]; 4] {
|
||||
[
|
||||
[1.41272, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.56969, 0.0, 0.0],
|
||||
[0.0, 0.0, -1.002, -1.0],
|
||||
[0.0, 0.0, -0.2002, 0.0],
|
||||
]
|
||||
}
|
||||
|
||||
/// This is the projection matrix k4aviewer starts with
|
||||
pub const fn initial_projection_matrix_4x4_flat() -> [f32; 16] {
|
||||
[
|
||||
1.41272, 0.0, 0.0, 0.0,
|
||||
0.0, 1.56969, 0.0, 0.0,
|
||||
0.0, 0.0, -1.002, -1.0,
|
||||
0.0, 0.0, -0.2002, 0.0,
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod compile_shader;
|
||||
pub mod debug;
|
||||
pub mod link_program;
|
||||
pub mod load_texture_from_file;
|
||||
pub mod matrices;
|
||||
pub mod rebinder;
|
||||
pub mod wrapper;
|
||||
@@ -0,0 +1,75 @@
|
||||
use gl;
|
||||
use gl::types::*;
|
||||
|
||||
/// Rebinder records the current state of OpenGL bindings so that it can restore them later.
|
||||
/// I'm writing this because the code I wrote/adapted is unfortunately a mess.
|
||||
pub struct Rebinder {
|
||||
texture_id: GLint,
|
||||
array_buffer_id: GLint,
|
||||
element_array_buffer_id: GLint,
|
||||
pixel_unpack_buffer_id: GLint,
|
||||
renderbuffer_binding: GLint,
|
||||
vertex_array_binding: GLint,
|
||||
draw_framebuffer_binding: GLint,
|
||||
read_framebuffer_binding: GLint,
|
||||
current_program: GLint,
|
||||
active_texture: GLint,
|
||||
is_depth_test: u8,
|
||||
}
|
||||
|
||||
impl Rebinder {
|
||||
/// Capture a current snapshot of the bindings.
|
||||
pub fn snapshot() -> Self {
|
||||
let mut bindings = Self {
|
||||
texture_id: 0,
|
||||
array_buffer_id: 0,
|
||||
element_array_buffer_id: 0,
|
||||
pixel_unpack_buffer_id: 0,
|
||||
renderbuffer_binding: 0,
|
||||
vertex_array_binding: 0,
|
||||
draw_framebuffer_binding: 0,
|
||||
read_framebuffer_binding: 0,
|
||||
current_program: 0,
|
||||
active_texture: 0,
|
||||
is_depth_test: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
gl::GetIntegerv(gl::TEXTURE_BINDING_2D, &mut bindings.texture_id);
|
||||
gl::GetIntegerv(gl::ARRAY_BUFFER_BINDING, &mut bindings.array_buffer_id);
|
||||
gl::GetIntegerv(gl::ELEMENT_ARRAY_BUFFER_BINDING, &mut bindings.element_array_buffer_id);
|
||||
gl::GetIntegerv(gl::PIXEL_UNPACK_BUFFER_BINDING, &mut bindings.pixel_unpack_buffer_id);
|
||||
gl::GetIntegerv(gl::RENDERBUFFER_BINDING, &mut bindings.renderbuffer_binding);
|
||||
gl::GetIntegerv(gl::VERTEX_ARRAY_BINDING, &mut bindings.vertex_array_binding);
|
||||
gl::GetIntegerv(gl::DRAW_FRAMEBUFFER_BINDING, &mut bindings.draw_framebuffer_binding);
|
||||
gl::GetIntegerv(gl::READ_FRAMEBUFFER_BINDING, &mut bindings.read_framebuffer_binding);
|
||||
gl::GetIntegerv(gl::CURRENT_PROGRAM, &mut bindings.current_program);
|
||||
gl::GetIntegerv(gl::ACTIVE_TEXTURE, &mut bindings.active_texture);
|
||||
gl::GetBooleanv(gl::DEPTH_TEST, &mut bindings.is_depth_test);
|
||||
}
|
||||
|
||||
bindings
|
||||
}
|
||||
|
||||
/// Restore the snapshot of the bindings.
|
||||
/// This consumes the rebinder.
|
||||
pub fn restore(self) {
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.texture_id as u32);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, self.array_buffer_id as u32);
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.element_array_buffer_id as u32);
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.pixel_unpack_buffer_id as u32);
|
||||
gl::BindRenderbuffer(gl::RENDERBUFFER, self.renderbuffer_binding as u32);
|
||||
gl::BindVertexArray(self.vertex_array_binding as u32);
|
||||
gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, self.draw_framebuffer_binding as u32);
|
||||
gl::BindFramebuffer(gl::READ_FRAMEBUFFER, self.read_framebuffer_binding as u32);
|
||||
gl::UseProgram(self.current_program as u32);
|
||||
gl::ActiveTexture(self.active_texture as u32);
|
||||
if self.is_depth_test == 0 {
|
||||
gl::Disable(gl::DEPTH_TEST);
|
||||
} else {
|
||||
gl::Enable(gl::DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use anyhow::bail;
|
||||
use gl::types::*;
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::CString;
|
||||
|
||||
/// A typesafe handle on attribute IDs with convenience methods.
|
||||
///
|
||||
/// Unlike "Uniforms", which are global in GLSL, "Attributes" are applied on a per-vertex basis.
|
||||
/// They can be used to convey typical information (eg. normals) or completely custom information
|
||||
/// (eg. tangents or whatever).
|
||||
///
|
||||
/// https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/attributes.php
|
||||
///
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct Attribute {
|
||||
id: GLint,
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
pub fn lookup(identifier: &str, opengl_program_id: GLuint) -> AnyhowResult<Self> {
|
||||
let id_cstr : CString = CString::new(identifier)?;
|
||||
let id_cstr_ptr : *const c_char = id_cstr.as_ptr() as *const c_char;
|
||||
|
||||
let mut loc = -1;
|
||||
unsafe {
|
||||
loc = gl::GetAttribLocation(opengl_program_id, id_cstr_ptr);
|
||||
}
|
||||
|
||||
if loc < 0 {
|
||||
bail!("Couldn't find attribute named '{}' in OpenGL program {}.", identifier, opengl_program_id);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
id: loc,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn id(&self) -> GLint {
|
||||
self.id
|
||||
}
|
||||
|
||||
/*pub fn set_uint(&self, value: GLuint) {
|
||||
let loc = self.id() as GLuint;
|
||||
unsafe {
|
||||
gl::VertexAttribI1ui(loc, value);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//! These wrappers make it easier to use certain OpenGL types.
|
||||
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
|
||||
//! particularly the code in `openglhelpers.h`.
|
||||
|
||||
use gl::types::*;
|
||||
|
||||
/// A slightly more convenient OpenGL Buffer type
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Buffer {
|
||||
id: GLuint,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn new() -> Self {
|
||||
return Self {
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_initialized() -> Self {
|
||||
let mut new_object= Self::new();
|
||||
new_object.init();
|
||||
new_object
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.id != 0
|
||||
}
|
||||
|
||||
pub fn id(&self) -> GLuint {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.reset();
|
||||
self.gen();
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
if self.is_initialized() {
|
||||
self.delete();
|
||||
self.id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_as_array_buffer(&self) {
|
||||
unsafe {
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, self.id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen(&mut self) {
|
||||
unsafe {
|
||||
gl::GenBuffers(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteBuffers(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod attribute;
|
||||
pub mod buffer;
|
||||
pub mod other_misc_wrapper;
|
||||
pub mod texture;
|
||||
pub mod uniform;
|
||||
pub mod vertex_array;
|
||||
@@ -0,0 +1,206 @@
|
||||
//! These wrappers make it easier to use certain OpenGL types.
|
||||
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
|
||||
//! particularly the code in `openglhelpers.h`.
|
||||
|
||||
use std::fmt::Formatter;
|
||||
use std::ptr::null;
|
||||
|
||||
use gl;
|
||||
use gl::types::*;
|
||||
|
||||
/// A slightly more convenient OpenGL Framebuffer type
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Framebuffer {
|
||||
id: GLuint,
|
||||
}
|
||||
|
||||
impl Framebuffer {
|
||||
pub fn new() -> Self {
|
||||
return Self {
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_initialized() -> Self {
|
||||
let mut new_object= Self::new();
|
||||
new_object.init();
|
||||
new_object
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.id != 0
|
||||
}
|
||||
|
||||
pub fn id(&self) -> GLuint {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.reset();
|
||||
self.gen();
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
if self.is_initialized() {
|
||||
self.delete();
|
||||
self.id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen(&mut self) {
|
||||
unsafe {
|
||||
gl::GenFramebuffers(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteFramebuffers(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A slightly more convenient OpenGL Renderbuffer type
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Renderbuffer {
|
||||
id: GLuint,
|
||||
}
|
||||
|
||||
impl Renderbuffer {
|
||||
pub fn new() -> Self {
|
||||
return Self {
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_initialized() -> Self {
|
||||
let mut new_object= Self::new();
|
||||
new_object.init();
|
||||
new_object
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.id != 0
|
||||
}
|
||||
|
||||
pub fn id(&self) -> GLuint {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.reset();
|
||||
self.gen();
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
if self.is_initialized() {
|
||||
self.delete();
|
||||
self.id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn gen(&mut self) {
|
||||
unsafe {
|
||||
gl::GenRenderbuffers(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteRenderbuffers(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the OpenGL errors returned by `glGetError()`.
|
||||
/// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetError.xhtml
|
||||
/// When an error flag is set, results of a GL operation are undefined only if GL_OUT_OF_MEMORY
|
||||
/// has occurred. In all other cases, the command generating the error is ignored and has no effect
|
||||
/// on the GL state or frame buffer contents. If the generating command returns a value, it
|
||||
/// returns 0. If glGetError itself generates an error, it returns 0.
|
||||
#[derive(Copy,Clone,Debug)]
|
||||
pub enum OpenGlError {
|
||||
/// No error has been recorded. The value of this symbolic constant is guaranteed to be 0.
|
||||
/// NB: While GL_NO_ERROR is represented here, it won't be returned by the convenience function.
|
||||
NoError,
|
||||
/// An unacceptable value is specified for an enumerated argument. The offending command is
|
||||
/// ignored and has no other side effect than to set the error flag.
|
||||
InvalidEnum,
|
||||
/// A numeric argument is out of range. The offending command is ignored and has no other side
|
||||
/// effect than to set the error flag.
|
||||
InvalidValue,
|
||||
/// The specified operation is not allowed in the current state. The offending command is
|
||||
/// ignored and has no other side effect than to set the error flag.
|
||||
InvalidOperation,
|
||||
/// The framebuffer object is not complete. The offending command is ignored and has no other
|
||||
/// side effect than to set the error flag.
|
||||
InvalidFramebufferOperation,
|
||||
/// There is not enough memory left to execute the command. The state of the GL is undefined,
|
||||
/// except for the state of the error flags, after this error is recorded.
|
||||
OutOfMemory,
|
||||
/// An attempt has been made to perform an operation that would cause an internal stack to
|
||||
/// underflow.
|
||||
StackUnderflow,
|
||||
/// An attempt has been made to perform an operation that would cause an internal stack to
|
||||
/// overflow.
|
||||
StackOverflow,
|
||||
/// Invented error if we can't determine which enum was returned.
|
||||
UnknownError(u32),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for OpenGlError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let explain = match self {
|
||||
OpenGlError::NoError => "OpenGL error: No Error (this shouldn't ever be set)".into(),
|
||||
OpenGlError::InvalidEnum => "OpenGL error: Invalid Enum".into(),
|
||||
OpenGlError::InvalidValue => "OpenGL error: Invalid Value".into(),
|
||||
OpenGlError::InvalidOperation => "OpenGL error: Invalid Operation".into(),
|
||||
OpenGlError::InvalidFramebufferOperation => "OpenGL error: Invalid Framebuffer Operation".into(),
|
||||
OpenGlError::OutOfMemory => "OpenGL error: Out of Memory".into(),
|
||||
OpenGlError::StackUnderflow => "OpenGL error: Stack Underflow".into(),
|
||||
OpenGlError::StackOverflow => "OpenGL error: Stack Overflow".into(),
|
||||
OpenGlError::UnknownError(result) => format!("OpenGL error: Unknown Error {}", result),
|
||||
};
|
||||
write!(f, "{}", explain)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for OpenGlError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
|
||||
// Generic error, no backtrace.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Query OpenGL for the current error.
|
||||
pub fn gl_get_error() -> Result<(), OpenGlError> {
|
||||
let result = unsafe {
|
||||
gl::GetError()
|
||||
};
|
||||
match result {
|
||||
gl::NO_ERROR => Ok(()),
|
||||
gl::INVALID_ENUM => Err(OpenGlError::InvalidEnum),
|
||||
gl::INVALID_VALUE => Err(OpenGlError::InvalidValue),
|
||||
gl::INVALID_OPERATION => Err(OpenGlError::InvalidOperation),
|
||||
gl::INVALID_FRAMEBUFFER_OPERATION => Err(OpenGlError::InvalidFramebufferOperation),
|
||||
gl::OUT_OF_MEMORY => Err(OpenGlError::OutOfMemory),
|
||||
gl::STACK_UNDERFLOW => Err(OpenGlError::StackUnderflow),
|
||||
gl::STACK_OVERFLOW => Err(OpenGlError::StackOverflow),
|
||||
_ => Err(OpenGlError::UnknownError(result)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the stride width for OpenGL
|
||||
/// Useful for `gl::VertexAttribPointer`.
|
||||
pub fn get_stride<T>(size: usize) -> gl::types::GLint {
|
||||
(size * std::mem::size_of::<T>()) as gl::types::GLint
|
||||
}
|
||||
|
||||
/// Calculate the offset for OpenGL
|
||||
/// Useful for `gl::VertexAttribPointer`.
|
||||
pub fn get_pointer_offset<T>(offset: usize) -> *const gl::types::GLvoid {
|
||||
match offset {
|
||||
0 => null(),
|
||||
_ => (offset * std::mem::size_of::<T>()) as *const gl::types::GLvoid,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//! These wrappers make it easier to use certain OpenGL types.
|
||||
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
|
||||
//! particularly the code in `openglhelpers.h`.
|
||||
|
||||
use gl::types::*;
|
||||
|
||||
/// A slightly more convenient OpenGL Texture type
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Texture {
|
||||
id: GLuint,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn new() -> Self {
|
||||
return Self {
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_initialized() -> Self {
|
||||
let mut new_object= Self::new();
|
||||
new_object.init();
|
||||
new_object
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.id != 0
|
||||
}
|
||||
|
||||
pub fn id(&self) -> GLuint {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.reset();
|
||||
self.gen();
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
if self.is_initialized() {
|
||||
self.delete();
|
||||
self.id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_as_texture_2d(&self) {
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn gen(&mut self) {
|
||||
unsafe {
|
||||
gl::GenTextures(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteTextures(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use anyhow::bail;
|
||||
use gl::types::*;
|
||||
use std::os::raw::c_char;
|
||||
use std::ffi::CString;
|
||||
|
||||
/// A typesafe handle on uniform IDs with convenience methods.
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct Uniform {
|
||||
id: GLint,
|
||||
}
|
||||
|
||||
impl Uniform {
|
||||
pub fn lookup(identifier: &str, opengl_program_id: GLuint) -> AnyhowResult<Self> {
|
||||
let id_cstr : CString = CString::new(identifier)?;
|
||||
let id_cstr_ptr : *const c_char = id_cstr.as_ptr() as *const c_char;
|
||||
|
||||
let mut loc = -1;
|
||||
unsafe {
|
||||
loc = gl::GetUniformLocation(opengl_program_id, id_cstr_ptr);
|
||||
}
|
||||
|
||||
if loc < 0 {
|
||||
bail!("Couldn't find uniform named '{}' in OpenGL program {}.", identifier, opengl_program_id);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
id: loc,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn id(&self) -> GLint {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//! These wrappers make it easier to use certain OpenGL types.
|
||||
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
|
||||
//! particularly the code in `openglhelpers.h`.
|
||||
|
||||
use gl::types::*;
|
||||
|
||||
/// A slightly more convenient OpenGL VertexArray type
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VertexArray {
|
||||
id: GLuint,
|
||||
}
|
||||
|
||||
impl VertexArray {
|
||||
pub fn new() -> Self {
|
||||
return Self {
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_initialized() -> Self {
|
||||
let mut new_object= Self::new();
|
||||
new_object.init();
|
||||
new_object
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.id != 0
|
||||
}
|
||||
|
||||
pub fn id(&self) -> GLuint {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
self.reset();
|
||||
self.gen();
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
if self.is_initialized() {
|
||||
self.delete();
|
||||
self.id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind(&self) {
|
||||
unsafe {
|
||||
gl::BindVertexArray(self.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn gen(&mut self) {
|
||||
unsafe {
|
||||
gl::GenVertexArrays(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteVertexArrays(1, &mut self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use crate::point_cloud::debug::image_proxy::ImageProxy;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::CaptureError;
|
||||
use kinect;
|
||||
use std::path::Path;
|
||||
use std::sync::{RwLock, Mutex, PoisonError, MutexGuard, Arc};
|
||||
use winit::event::VirtualKeyCode::Mute;
|
||||
|
||||
/// Store either a wrapped capture, or a k4a Capture.
|
||||
/// Useful for debugging.
|
||||
#[derive(Clone)]
|
||||
pub struct CaptureProxy {
|
||||
storage: UnderlyingStorage,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum UnderlyingStorage {
|
||||
CameraImageBytes {
|
||||
color_image: ImageProxy,
|
||||
depth_image: ImageProxy,
|
||||
},
|
||||
K4aCapture {
|
||||
capture: kinect::Capture,
|
||||
maybe_color_image: Option<ImageProxy>,
|
||||
maybe_depth_image: Option<ImageProxy>,
|
||||
}
|
||||
}
|
||||
|
||||
impl CaptureProxy {
|
||||
|
||||
pub fn from_image_proxy_pair(color_image: ImageProxy, depth_image: ImageProxy) -> Self {
|
||||
Self {
|
||||
storage: UnderlyingStorage::CameraImageBytes {
|
||||
color_image,
|
||||
depth_image,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_k4a_capture(capture: &kinect::Capture) -> Self {
|
||||
// NB: We need to increase the refcount.
|
||||
// K4a manages the memory under the hood.
|
||||
let capture = capture.clone();
|
||||
Self::consume_k4a_capture(capture)
|
||||
}
|
||||
|
||||
pub fn consume_k4a_capture(capture: kinect::Capture) -> Self {
|
||||
// NB: I tried to lazily unpack these, but interior mutability Sync/Send was a nightmare.
|
||||
// The poor ergonomics were not worth it.
|
||||
let color_image = capture.get_color_image()
|
||||
.map(|image| ImageProxy::consume_k4a_image(image))
|
||||
.ok();
|
||||
|
||||
let depth_image = capture.get_depth_image()
|
||||
.map(|image| ImageProxy::consume_k4a_image(image))
|
||||
.ok();
|
||||
|
||||
Self {
|
||||
storage: UnderlyingStorage::K4aCapture {
|
||||
capture,
|
||||
maybe_color_image: color_image,
|
||||
maybe_depth_image: depth_image,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_k4a(&self) -> bool {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::CameraImageBytes{ .. } => false,
|
||||
UnderlyingStorage::K4aCapture { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_color_image(&self) -> AnyhowResult<&ImageProxy> {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::CameraImageBytes { color_image, .. } => Ok(color_image),
|
||||
UnderlyingStorage::K4aCapture{ maybe_color_image, .. } => {
|
||||
let inner = maybe_color_image.as_ref()
|
||||
.ok_or(CaptureError::NullCapture)?;
|
||||
Ok(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_depth_image(&self) -> AnyhowResult<&ImageProxy> {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::CameraImageBytes { depth_image, .. } => Ok(depth_image),
|
||||
UnderlyingStorage::K4aCapture{ maybe_depth_image, .. } => {
|
||||
let inner = maybe_depth_image.as_ref()
|
||||
.ok_or(CaptureError::NullCapture)?;
|
||||
Ok(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_get_capture(&self) -> kinect::Capture {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::CameraImageBytes{ .. } => unimplemented!("Cannot get capture for non-k4a"),
|
||||
UnderlyingStorage::K4aCapture { capture, .. } => capture.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_save_files(&self, path_prefix: &Path, suffix: &str) -> AnyhowResult<()> {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::CameraImageBytes{ .. } => unimplemented!("No need to debug save for raw bytes"),
|
||||
UnderlyingStorage::K4aCapture { maybe_color_image, maybe_depth_image, .. } => {
|
||||
|
||||
if let Some(color_image) = maybe_color_image {
|
||||
let filename = path_prefix.join(format!("./color_frame_{}", suffix));
|
||||
color_image.debug_save_file(filename.to_str().unwrap())?;
|
||||
}
|
||||
|
||||
if let Some(depth_image) = maybe_depth_image {
|
||||
let filename = path_prefix.join(format!("./depth_frame_{}", suffix));
|
||||
depth_image.debug_save_file(filename.to_str().unwrap())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use crate::files::write_to_file_from_byte_ptr::write_to_file_from_byte_ptr;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::ImageFormat;
|
||||
use kinect;
|
||||
use std::fs::File;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
/// Store either raw bytes, or wrap a k4a::Image
|
||||
/// This is meant to be plumbed through the system instead of a k4a::Image (depth or color image)
|
||||
#[derive(Clone)]
|
||||
pub struct ImageProxy {
|
||||
storage: UnderlyingStorage,
|
||||
}
|
||||
|
||||
/// The underlying data store
|
||||
#[derive(Clone)]
|
||||
enum UnderlyingStorage {
|
||||
/// Our own serialization
|
||||
Bytes {
|
||||
bytes: Vec<u8>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
stride_bytes: usize,
|
||||
format: ImageFormat,
|
||||
},
|
||||
/// From the Kinect camera
|
||||
K4aImage(kinect::Image),
|
||||
}
|
||||
|
||||
impl ImageProxy {
|
||||
|
||||
pub fn from_file(
|
||||
filename: &str,
|
||||
width: usize,
|
||||
height: usize,
|
||||
stride_bytes: usize,
|
||||
format: ImageFormat) -> AnyhowResult<Self>
|
||||
{
|
||||
let mut file = File::open(filename)?;
|
||||
let metadata = fs::metadata(&filename)?;
|
||||
|
||||
let mut buffer = vec![0u8; metadata.len() as usize];
|
||||
file.read(&mut buffer)?;
|
||||
|
||||
let storage = UnderlyingStorage::Bytes {
|
||||
bytes: buffer,
|
||||
width,
|
||||
height,
|
||||
stride_bytes,
|
||||
format,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
storage,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_k4a_image(image: &kinect::Image) -> Self {
|
||||
// NB: We need to increase the refcount.
|
||||
// K4a manages the memory under the hood.
|
||||
let image = image.clone();
|
||||
let storage = UnderlyingStorage::K4aImage(image);
|
||||
Self {
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume_k4a_image(image: kinect::Image) -> Self {
|
||||
let storage = UnderlyingStorage::K4aImage(image);
|
||||
Self {
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_k4a(&self) -> bool {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { .. } => false,
|
||||
UnderlyingStorage::K4aImage(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { bytes, .. } => bytes.len(),
|
||||
UnderlyingStorage::K4aImage(image) => image.get_size(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes{ ref bytes, .. } => {
|
||||
bytes.as_ptr()
|
||||
},
|
||||
UnderlyingStorage::K4aImage(ref image) => {
|
||||
image.get_buffer() as *const u8
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_buffer(&self) -> *const u8 {
|
||||
self.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_width_pixels(&self) -> usize {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { width, .. } => *width,
|
||||
UnderlyingStorage::K4aImage(image) => image.get_width_pixels(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_height_pixels(&self) -> usize {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { height, .. } => *height,
|
||||
UnderlyingStorage::K4aImage(image) => image.get_height_pixels(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_stride_bytes(&self) -> usize {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { stride_bytes, .. } => *stride_bytes,
|
||||
UnderlyingStorage::K4aImage(image) => image.get_stride_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_format(&self) -> ImageFormat {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { format, .. } => *format,
|
||||
UnderlyingStorage::K4aImage(image) => image.get_format(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_handle(&self) -> k4a_sys::k4a_image_t {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { bytes, width, height, stride_bytes, format } => {
|
||||
// TODO: We're not handing over ownership of the buffer!
|
||||
// The k4a_image cannot be used after this ImageProxy dies!
|
||||
let mut handle = null_mut();
|
||||
|
||||
//println!("fake.get_handle(): {:?}, {}, {}x{}", format, stride_bytes, width, height);
|
||||
|
||||
unsafe {
|
||||
k4a_sys::k4a_image_create_from_buffer(
|
||||
format.to_k4a(),
|
||||
*width as i32,
|
||||
*height as i32,
|
||||
*stride_bytes as i32,
|
||||
bytes.as_ptr() as *mut u8, // TODO: Technically we won't write.
|
||||
bytes.len(),
|
||||
None,
|
||||
null_mut(),
|
||||
&mut handle,
|
||||
);
|
||||
}
|
||||
|
||||
handle
|
||||
},
|
||||
UnderlyingStorage::K4aImage(image) => {
|
||||
//println!("real.get_handle(): {:?}, {}, {}x{}", image.get_format(), image.get_stride_bytes(), image.get_width_pixels(), image.get_height_pixels());
|
||||
image.get_handle()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_save_file(&self, filename: &str) -> AnyhowResult<()> {
|
||||
match &self.storage {
|
||||
UnderlyingStorage::Bytes { .. } => unimplemented!("No need to debug save raw bytes"),
|
||||
UnderlyingStorage::K4aImage(image) => {
|
||||
let byte_src = image.get_buffer();
|
||||
let size_bytes = image.get_size();
|
||||
write_to_file_from_byte_ptr(filename, byte_src, size_bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod image_proxy;
|
||||
pub mod capture_proxy;
|
||||
@@ -0,0 +1,564 @@
|
||||
//! This is a port of Microsoft's libk4a `gpudepthtopointcloudconverter.h`.
|
||||
//! This code turns depth images into point clouds.
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::fmt::Formatter;
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::ptr;
|
||||
use std::ptr::null;
|
||||
use std::str;
|
||||
|
||||
use gl;
|
||||
use gl::types::*;
|
||||
|
||||
use crate::files::read_file_string_contents::read_file_string_contents;
|
||||
use crate::files::write_to_file_from_byte_ptr::write_to_file_from_byte_ptr;
|
||||
use crate::opengl::compile_shader::compile_shader;
|
||||
use crate::opengl::wrapper::buffer::Buffer;
|
||||
use crate::opengl::wrapper::other_misc_wrapper::OpenGlError;
|
||||
use crate::opengl::wrapper::other_misc_wrapper::gl_get_error;
|
||||
use crate::opengl::wrapper::texture::Texture;
|
||||
use crate::point_cloud::debug::image_proxy::ImageProxy;
|
||||
use crate::point_cloud::pixel_structs::DepthPixel;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::{Image, Calibration, ImageFormat};
|
||||
use kinect;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, PointCloudComputeError>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PointCloudComputeError {
|
||||
OpenGlError(OpenGlError),
|
||||
UnknownError,
|
||||
}
|
||||
|
||||
impl From<OpenGlError> for PointCloudComputeError {
|
||||
fn from(error: OpenGlError) -> Self {
|
||||
PointCloudComputeError::OpenGlError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PointCloudComputeError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let description = match self {
|
||||
PointCloudComputeError::OpenGlError(inner) => {
|
||||
format!("Compute OpenGL error: {}", inner)
|
||||
},
|
||||
PointCloudComputeError::UnknownError => "Unknown Compute Error".into(),
|
||||
};
|
||||
|
||||
write!(f, "{}", description)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for PointCloudComputeError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
|
||||
// Generic error, no backtrace.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The format that the point cloud texture uses internally to store points.
|
||||
/// If you want to use the texture that this outputs from your shader, you
|
||||
/// need to pass this as the format argument to glBindImageTexture().
|
||||
pub const POINT_CLOUD_TEXTURE_FORMAT : GLuint = gl::RGBA32F;
|
||||
|
||||
pub struct GpuPointCloudConverter {
|
||||
/// The OpenGL program
|
||||
shader_program_id: GLuint,
|
||||
|
||||
/// The x,y table as a texture
|
||||
pub xy_table_texture: Texture,
|
||||
|
||||
/// Preallocated texture for depth image so we don't have to reallocate every frame.
|
||||
pub depth_image_texture: Texture,
|
||||
|
||||
depth_image_pixel_buffer: Buffer,
|
||||
|
||||
/// Uniform location in the shader program
|
||||
// TODO: Turns out this does nothing
|
||||
//dest_tex_id: GLint,
|
||||
|
||||
/// Uniform location in the shader program
|
||||
xy_table_id: GLint,
|
||||
|
||||
/// Uniform location in the shader program
|
||||
depth_image_id: GLint,
|
||||
}
|
||||
|
||||
impl GpuPointCloudConverter {
|
||||
|
||||
pub fn new() -> Self {
|
||||
let program_id = unsafe { gl::CreateProgram() };
|
||||
|
||||
let point_cloud_compute_shader = read_file_string_contents("src/point_cloud/shaders/point_cloud_compute_shader.glsl").unwrap();
|
||||
|
||||
let shader_id = compile_shader(&point_cloud_compute_shader, gl::COMPUTE_SHADER);
|
||||
|
||||
link_program(program_id, shader_id);
|
||||
|
||||
let mut dest_tex_id = 0;
|
||||
let mut xy_table_id = 0;
|
||||
let mut depth_image_id = 0;
|
||||
|
||||
/// Uniform variable name in OpenGL shader program
|
||||
// TODO: Turns out this does nothing.
|
||||
let DEST_TEX : CString = CString::new("destTex").expect("string is correct");
|
||||
let DEST_TEX_PTR : *const c_char = DEST_TEX.as_ptr() as *const c_char;
|
||||
|
||||
/// Uniform variable name in OpenGL shader program
|
||||
let XY_TABLE : CString = CString::new("xyTable").expect("string is correct");
|
||||
let XY_TABLE_PTR: *const c_char = XY_TABLE.as_ptr() as *const c_char;
|
||||
|
||||
/// Uniform variable name in OpenGL shader program
|
||||
let DEPTH_IMAGE : CString = CString::new("depthImage").expect("string is correct");
|
||||
let DEPTH_IMAGE_PTR : *const c_char = DEPTH_IMAGE.as_ptr() as *const c_char;
|
||||
|
||||
unsafe {
|
||||
dest_tex_id = gl::GetUniformLocation(program_id, DEST_TEX_PTR);
|
||||
xy_table_id = gl::GetUniformLocation(program_id, XY_TABLE_PTR);
|
||||
depth_image_id = gl::GetUniformLocation(program_id, DEPTH_IMAGE_PTR);
|
||||
}
|
||||
|
||||
GpuPointCloudConverter {
|
||||
shader_program_id: program_id,
|
||||
//shader_id,
|
||||
//dest_tex_id,
|
||||
xy_table_id,
|
||||
depth_image_id,
|
||||
depth_image_texture: Texture::new(),
|
||||
xy_table_texture: Texture::new(),
|
||||
depth_image_pixel_buffer: Buffer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes depth data and turns it into a texture containing the XYZ coordinates of the depth map
|
||||
/// using the most recently set-to-active XY table. The input depth image and output texture
|
||||
/// (if already set) must be of the same resolution that was used to generate that XY table, or
|
||||
/// else behavior is undefined.
|
||||
///
|
||||
/// Essentially a reimplementation of k4a::transform::depth_image_to_point_cloud on the GPU.
|
||||
/// This is much more performant than k4a::transform::depth_image_to_point_cloud, but is a bit
|
||||
/// more unwieldly to use since you have to use its output in shaders.
|
||||
///
|
||||
/// The output texture has an internal format of GL_RGBA32F and is intended to be used directly
|
||||
/// by other OpenGL shaders as an image2d uniform.
|
||||
///
|
||||
/// To avoid excess image allocations, you can reuse a texture that was previously output
|
||||
/// by this function, provided the depth image and XY table previously used was for the same
|
||||
/// sized texture.
|
||||
///
|
||||
pub fn convert(&self,
|
||||
//depth_image: &kinect::Image,
|
||||
depth_image: &ImageProxy,
|
||||
output_texture: &mut Texture,
|
||||
camera_index: usize
|
||||
) -> Result<()> {
|
||||
if !self.xy_table_texture.is_initialized() {
|
||||
return Err(PointCloudComputeError::UnknownError);
|
||||
}
|
||||
|
||||
// Create output texture if it doesn't already exist
|
||||
//
|
||||
// We don't use the alpha channel, but it turns out OpenGL doesn't
|
||||
// actually have a 3-component (i.e. RGB32F) format - you get
|
||||
// 1, 2, or 4 components.
|
||||
//
|
||||
let width = depth_image.get_width_pixels() as i32;
|
||||
let height = depth_image.get_height_pixels() as i32;
|
||||
|
||||
if !output_texture.is_initialized() {
|
||||
output_texture.init();
|
||||
|
||||
unsafe {
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
gl::BindTexture(gl::TEXTURE_2D, output_texture.id());
|
||||
|
||||
// The format that the point cloud texture uses internally to store points.
|
||||
// If you want to use the texture that this outputs from your shader, you
|
||||
// need to pass this as the format argument to glBindImageTexture().
|
||||
// static constexpr GLenum PointCloudTextureFormat = GL_RGBA32F;
|
||||
// gl::TexStorage2D(gl::TEXTURE_2D, 1, POINT_CLOUD_TEXTURE_FORMAT, width, height);
|
||||
gl::TexStorage2D(gl::TEXTURE_2D, 1, gl::RGBA32F, width, height);
|
||||
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Upload data to uniform texture
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.depth_image_pixel_buffer.id());
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.depth_image_texture.id());
|
||||
|
||||
let num_bytes: GLuint = (width * height * size_of::<u16>() as i32) as GLuint; // libc::uint16_t = u16
|
||||
let length = (width * height) as usize;
|
||||
//let num_bytes: GLuint = (width * height * size_of::<DepthPixel>() as i32) as GLuint; // libc::uint16_t = u16
|
||||
|
||||
// GLubyte *textureMappedBuffer = reinterpret_cast<GLubyte *>(...)
|
||||
let texture_mapped_buffer = gl::MapBufferRange(
|
||||
gl::PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
num_bytes as isize,
|
||||
//(std::mem::size_of::<DepthPixel>() * length) as GLsizeiptr,
|
||||
gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT
|
||||
//);
|
||||
) as *mut DepthPixel;
|
||||
|
||||
if texture_mapped_buffer as usize == 0 {
|
||||
return Err(PointCloudComputeError::UnknownError);
|
||||
}
|
||||
|
||||
let depth_image_buffer = depth_image.get_buffer();
|
||||
|
||||
//let filename = format!("output/depth_src_{}", camera_index);
|
||||
//let size = (width * height) as usize * size_of::<u16>();
|
||||
//write_to_file_from_byte_ptr(&filename, depth_image_buffer, size).unwrap();
|
||||
|
||||
let typed_depth_image_buffer = depth_image_buffer as *const DepthPixel;
|
||||
|
||||
std::ptr::copy::<DepthPixel>(typed_depth_image_buffer, texture_mapped_buffer, length as usize);
|
||||
|
||||
let result = gl::UnmapBuffer(gl::PIXEL_UNPACK_BUFFER);
|
||||
if result == gl::FALSE {
|
||||
return Err(PointCloudComputeError::UnknownError);
|
||||
}
|
||||
|
||||
gl::TexSubImage2D(
|
||||
gl::TEXTURE_2D, // target
|
||||
0, // level
|
||||
0, // x offset
|
||||
0, // y offset
|
||||
width,
|
||||
height,
|
||||
gl::RED_INTEGER, //constexpr GLenum depthImageDataFormat = GL_RED_INTEGER;
|
||||
gl::UNSIGNED_SHORT, //constexpr GLenum depthImageDataType = GL_UNSIGNED_SHORT;
|
||||
null(), // data
|
||||
);
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
gl::UseProgram(self.shader_program_id);
|
||||
|
||||
// Bind textures that we're going to pass to the texture
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
gl::BindTexture(gl::TEXTURE_2D, output_texture.id());
|
||||
gl::BindImageTexture(
|
||||
0,
|
||||
output_texture.id(),
|
||||
0,
|
||||
gl::FALSE,
|
||||
0,
|
||||
gl::WRITE_ONLY,
|
||||
//POINT_CLOUD_TEXTURE_FORMAT
|
||||
//static constexpr GLenum PointCloudTextureFormat = GL_RGBA32F;
|
||||
gl::RGBA32F,
|
||||
);
|
||||
|
||||
gl::ActiveTexture(gl::TEXTURE1);
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.depth_image_texture.id());
|
||||
gl::BindImageTexture(
|
||||
1,
|
||||
self.depth_image_texture.id(),
|
||||
0,
|
||||
gl::FALSE,
|
||||
0,
|
||||
gl::READ_ONLY,
|
||||
gl::R16UI, //constexpr GLenum depthImageInternalFormat = GL_R16UI;
|
||||
);
|
||||
gl::Uniform1i(self.depth_image_id, 1);
|
||||
|
||||
gl::ActiveTexture(gl::TEXTURE2);
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.xy_table_texture.id());
|
||||
gl::BindImageTexture(
|
||||
2,
|
||||
self.xy_table_texture.id(),
|
||||
0,
|
||||
gl::FALSE,
|
||||
0,
|
||||
gl::READ_ONLY,
|
||||
gl::RG32F, //constexpr GLenum xyTableInternalFormat = GL_RG32F;
|
||||
);
|
||||
gl::Uniform1i(self.xy_table_id, 2);
|
||||
|
||||
gl::DispatchCompute(
|
||||
depth_image.get_width_pixels() as u32,
|
||||
depth_image.get_height_pixels() as u32,
|
||||
1,
|
||||
);
|
||||
|
||||
// Wait for the rendering to finish before allowing reads to the texture we just wrote
|
||||
gl::MemoryBarrier(gl::TEXTURE_FETCH_BARRIER_BIT);
|
||||
|
||||
gl_get_error()?;
|
||||
|
||||
// NB(bt): Restore to default active texture? Looks like this is required for 'imgui'
|
||||
// This is needed despite using glGetInteger(GL_ACTIVE_TEXTURE) and resetting that value.
|
||||
//gl::ActiveTexture(gl::TEXTURE0);
|
||||
|
||||
/*
|
||||
// Try to save the texture buffer to file... (not working yet)
|
||||
let size_bytes = (width * height * 4);
|
||||
let mut vec = vec![0u8; size_bytes as usize];
|
||||
let mut pointer = vec.as_mut_ptr() as *mut c_void;
|
||||
|
||||
// Pull the texture out as bytes.
|
||||
//gl::ActiveTexture(gl::TEXTURE1);
|
||||
gl::BindTexture(gl::TEXTURE_2D, output_texture.id());
|
||||
|
||||
gl_get_error().unwrap();
|
||||
|
||||
gl::GetTextureImage(
|
||||
output_texture.id(),
|
||||
0, // level
|
||||
gl::RGBA32F, // format
|
||||
gl::UNSIGNED_BYTE, // type
|
||||
size_bytes, // buffer size
|
||||
pointer,
|
||||
);
|
||||
|
||||
gl_get_error().unwrap();
|
||||
*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the XY table that will be used by future calls to Convert(). Get an XY table by calling
|
||||
/// GenerateXyTable().
|
||||
pub fn set_active_xy_table(&mut self, xy_table: &ImageProxy) -> Result<()> {
|
||||
let width = xy_table.get_width_pixels() as i32;
|
||||
let height = xy_table.get_height_pixels() as i32;
|
||||
|
||||
// Upload the XY table as a texture so we can use it as a uniform
|
||||
self.xy_table_texture.init();
|
||||
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.xy_table_texture.id());
|
||||
gl::TexStorage2D(
|
||||
gl::TEXTURE_2D,
|
||||
1,
|
||||
gl::RG32F, // constexpr GLenum xyTableInternalFormat = GL_RG32F;
|
||||
width,
|
||||
height,
|
||||
);
|
||||
|
||||
let xy_table_buffer = xy_table.get_buffer();
|
||||
|
||||
gl::TexSubImage2D(
|
||||
gl::TEXTURE_2D,
|
||||
0, // level
|
||||
0, // xoffset
|
||||
0, // yoffset
|
||||
width,
|
||||
height,
|
||||
gl::RG, //constexpr GLenum xyTableDataFormat = GL_RG;
|
||||
gl::FLOAT, //constexpr GLenum xyTableDataType = GL_FLOAT;
|
||||
xy_table_buffer as *const c_void,
|
||||
);
|
||||
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
|
||||
}
|
||||
|
||||
// Pre-allocate a texture for the depth images so we don't have to
|
||||
// reallocate on every frame
|
||||
self.depth_image_texture.init();
|
||||
self.depth_image_pixel_buffer.init();
|
||||
|
||||
let depth_image_size_bytes = (width * height * size_of::<u16>() as i32) as isize; // libc::uint16_t = u16
|
||||
|
||||
unsafe {
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.depth_image_pixel_buffer.id());
|
||||
gl::BufferData(
|
||||
gl::PIXEL_UNPACK_BUFFER,
|
||||
depth_image_size_bytes,
|
||||
ptr::null_mut(),
|
||||
gl::STREAM_DRAW,
|
||||
);
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.depth_image_texture.id());
|
||||
|
||||
gl::TexStorage2D(
|
||||
gl::TEXTURE_2D,
|
||||
1,
|
||||
gl::R16UI, // constexpr GLenum depthImageInternalFormat = GL_R16UI;
|
||||
width,
|
||||
height,
|
||||
);
|
||||
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
|
||||
}
|
||||
|
||||
gl_get_error()
|
||||
.map_err(|err| PointCloudComputeError::OpenGlError(err))
|
||||
}
|
||||
|
||||
/// Creates a k4a::image containing the XY tables from calibration based on calibrationType.
|
||||
/// The table is a 2D array of k4a_float2_t's with the same resolution as the camera of calibrationType
|
||||
/// specified in calibration.
|
||||
///
|
||||
/// You can use this table to convert a depth image into a point cloud, e.g. by using the Convert method.
|
||||
/// Conversion is done by multiplying the depth pixel value by the XY table values - i.e. the result
|
||||
/// pixel will be (xyTable[p].x * depthImage[p], xyTable[p].y * depthImage[p], depthImage[p]), where
|
||||
/// p is the index of a given pixel.
|
||||
pub fn generate_xy_table(calibration: &Calibration,
|
||||
calibration_type: k4a_sys::k4a_calibration_type_t)
|
||||
-> Result<Image>
|
||||
{
|
||||
let camera_calibration : k4a_sys::k4a_calibration_camera_t = match calibration_type {
|
||||
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_COLOR => calibration.0.color_camera_calibration,
|
||||
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH => calibration.0.depth_camera_calibration,
|
||||
_ => return Err(PointCloudComputeError::UnknownError),
|
||||
};
|
||||
|
||||
let width = camera_calibration.resolution_width as u32;
|
||||
let height = camera_calibration.resolution_height as u32;
|
||||
let stride_bytes = width * size_of::<k4a_sys::k4a_float2_t>() as u32;
|
||||
|
||||
let xy_table = Image::create(
|
||||
ImageFormat::Custom,
|
||||
width,
|
||||
height,
|
||||
stride_bytes,
|
||||
).map_err(|_| PointCloudComputeError::UnknownError)?;
|
||||
|
||||
// typedef union
|
||||
// {
|
||||
// // XY or array representation of vector
|
||||
// struct _xy
|
||||
// {
|
||||
// float x; // < X component of a vector
|
||||
// float y; // < Y component of a vector
|
||||
// } xy; // < X, Y representation of a vector
|
||||
// float v[2]; // < Array representation of a vector
|
||||
// } k4a_float2_t;
|
||||
let mut p = k4a_sys::k4a_float2_t {
|
||||
xy: k4a_sys::k4a_float2_t__xy {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
}
|
||||
};
|
||||
|
||||
// typedef union
|
||||
// {
|
||||
// // XYZ or array representation of vector.
|
||||
// struct _xyz
|
||||
// {
|
||||
// float x; // < X component of a vector.
|
||||
// float y; // < Y component of a vector.
|
||||
// float z; // < Z component of a vector.
|
||||
// } xyz; // < X, Y, Z representation of a vector.
|
||||
// float v[3]; // < Array representation of a vector.
|
||||
// } k4a_float3_t;
|
||||
let mut ray = k4a_sys::k4a_float3_t {
|
||||
xyz: k4a_sys::k4a_float3_t__xyz {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
}
|
||||
};
|
||||
|
||||
let mut idx = 0;
|
||||
unsafe {
|
||||
let table_data = xy_table.get_buffer();
|
||||
let typed_buffer = table_data as *mut k4a_sys::k4a_float2_t;
|
||||
|
||||
for y in 0..height {
|
||||
p.xy.y = y as f32;
|
||||
|
||||
for x in 0..width {
|
||||
p.xy.x = x as f32;
|
||||
|
||||
let mut valid: c_int = -1;
|
||||
let result = unsafe {
|
||||
// https://docs.rs/k4a-sys/0.2.0/k4a_sys/fn.k4a_calibration_2d_to_3d.html
|
||||
k4a_sys::k4a_calibration_2d_to_3d(
|
||||
&calibration.0,
|
||||
&p, // source point 2d
|
||||
1.0, // source depth mm
|
||||
calibration_type, // source camera
|
||||
calibration_type, // target camera
|
||||
&mut ray, // target point3d mm
|
||||
&mut valid // set to 1 when valid result, 0 when coordinate is not valid
|
||||
)
|
||||
};
|
||||
|
||||
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
|
||||
return Err(PointCloudComputeError::UnknownError);
|
||||
}
|
||||
|
||||
if valid == 1 {
|
||||
unsafe {
|
||||
/*
|
||||
println!("[size: {}x{}] 2D coord {}x{} --> 3D cord {}x{}x{}",
|
||||
width, height, p.xy.x, p.xy.y, ray.xyz.x, ray.xyz.y, ray.xyz.z);
|
||||
[size: 1280x720] 2D coord 0x0 --> 3D cord -1.0120127x-0.5833072x1
|
||||
[size: 1280x720] 2D coord 1x0 --> 3D cord -1.010335x-0.5832499x1
|
||||
[size: 1280x720] 2D coord 2x0 --> 3D cord -1.0086561x-0.58319193x1
|
||||
...
|
||||
[size: 1280x720] 2D coord 1277x719 --> 3D cord 1.0083642x0.55422443x1
|
||||
[size: 1280x720] 2D coord 1278x719 --> 3D cord 1.0100702x0.55429035x1
|
||||
[size: 1280x720] 2D coord 1279x719 --> 3D cord 1.0117699x0.55435246x1
|
||||
...
|
||||
[size: 640x576] 2D coord 0x0 --> 3D cord -0.97488284x-1.0173141x1
|
||||
[size: 640x576] 2D coord 1x0 --> 3D cord -0.97017723x-1.0155252x1
|
||||
[size: 640x576] 2D coord 2x0 --> 3D cord -0.9654954x-1.0137504x1
|
||||
...
|
||||
[size: 640x576] 2D coord 637x575 --> 3D cord 0.7947599x0.60008353x1
|
||||
[size: 640x576] 2D coord 638x575 --> 3D cord 0.79833484x0.60085475x1
|
||||
[size: 640x576] 2D coord 639x575 --> 3D cord 0.8019226x0.60163087x1
|
||||
*/
|
||||
(*typed_buffer.offset(idx)).xy.x = ray.xyz.x;
|
||||
(*typed_buffer.offset(idx)).xy.y = ray.xyz.y;
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
// This pixel is invalid
|
||||
(*typed_buffer.offset(idx)).xy.x = 0.0;
|
||||
(*typed_buffer.offset(idx)).xy.y = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(xy_table)
|
||||
}
|
||||
}
|
||||
|
||||
fn link_program(program: GLuint, shader: GLuint) -> GLuint {
|
||||
unsafe {
|
||||
gl::AttachShader(program, shader);
|
||||
gl::LinkProgram(program);
|
||||
|
||||
// Get the link status
|
||||
let mut status = gl::FALSE as GLint;
|
||||
gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);
|
||||
|
||||
// Fail on error
|
||||
if status != (gl::TRUE as GLint) {
|
||||
let mut len: GLint = 0;
|
||||
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
|
||||
let mut buf = Vec::with_capacity(len as usize);
|
||||
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
|
||||
gl::GetProgramInfoLog(
|
||||
program,
|
||||
len,
|
||||
ptr::null_mut(),
|
||||
buf.as_mut_ptr() as *mut GLchar,
|
||||
);
|
||||
panic!(
|
||||
"{}",
|
||||
str::from_utf8(&buf)
|
||||
.ok()
|
||||
.expect("ProgramInfoLog not valid utf8")
|
||||
);
|
||||
}
|
||||
program
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod debug;
|
||||
pub mod gpu_point_cloud_converter;
|
||||
pub mod pixel_structs;
|
||||
pub mod point_cloud_renderer;
|
||||
pub mod point_cloud_visualiser;
|
||||
pub mod util;
|
||||
pub mod viewer_image;
|
||||
@@ -0,0 +1,12 @@
|
||||
use libc::uint16_t;
|
||||
use libc::uint8_t;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct BgraPixel {
|
||||
pub blue: uint8_t,
|
||||
pub green: uint8_t,
|
||||
pub red: uint8_t,
|
||||
pub alpha: uint8_t,
|
||||
}
|
||||
|
||||
pub type DepthPixel = uint16_t;
|
||||
@@ -0,0 +1,453 @@
|
||||
//! This is a port of Microsoft's libk4a `k4apointcloudrenderer.cpp`.
|
||||
//! This provides the visual output.
|
||||
|
||||
use anyhow::Result as AnyhowResult;
|
||||
use arcball::ArcballCamera;
|
||||
use crate::assets::obj_loader::{load_wavefront, ExtractedVertex};
|
||||
use crate::assets::renderable_object::RenderableObject;
|
||||
use crate::files::read_file_string_contents::read_file_string_contents;
|
||||
use crate::files::write_to_file_from_byte_ptr::write_to_file_from_byte_ptr;
|
||||
use crate::gui::mouse_camera_arcball::MouseCameraArcball;
|
||||
use crate::opengl::compile_shader::compile_shader;
|
||||
use crate::opengl::link_program::link_shader_program;
|
||||
use crate::opengl::wrapper::buffer::Buffer;
|
||||
use crate::opengl::wrapper::other_misc_wrapper::{gl_get_error, OpenGlError};
|
||||
use crate::opengl::wrapper::texture::Texture;
|
||||
use crate::opengl::wrapper::vertex_array::VertexArray;
|
||||
use crate::point_cloud::debug::image_proxy::ImageProxy;
|
||||
use crate::point_cloud::pixel_structs::BgraPixel;
|
||||
use gl::types::*;
|
||||
use gl;
|
||||
use gltf::Gltf;
|
||||
use std::ffi::CString;
|
||||
use std::fmt::Formatter;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write, BufReader};
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::{c_char, c_void};
|
||||
use std::path::Path;
|
||||
use std::ptr::{null, null_mut};
|
||||
use std::ptr;
|
||||
use std::str;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tobj::{load_obj, load_mtl};
|
||||
use crate::assets::positionable_object::{PositionableObject, ObjectType};
|
||||
use cgmath::{Matrix4, SquareMatrix, Matrix};
|
||||
use crate::opengl::wrapper::uniform::Uniform;
|
||||
use crate::opengl::matrices::{initial_projection_matrix_4x4_flat, initial_view_matrix_4x4_flat, identity_matrix_4x4, initial_view_matrix_4x4, initial_projection_matrix_4x4};
|
||||
use image::DynamicImage;
|
||||
use crate::opengl::wrapper::attribute::Attribute;
|
||||
use nalgebra::RealField;
|
||||
use cgmath::num_traits::ToPrimitive;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, PointCloudRendererError>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PointCloudRendererError {
|
||||
OpenGlError(OpenGlError),
|
||||
UnknownError,
|
||||
}
|
||||
|
||||
impl From<OpenGlError> for PointCloudRendererError {
|
||||
fn from(error: OpenGlError) -> Self {
|
||||
PointCloudRendererError::OpenGlError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PointCloudRendererError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let description = match self {
|
||||
PointCloudRendererError::OpenGlError(inner) => {
|
||||
format!("Renderer OpenGL error: {}", inner)
|
||||
},
|
||||
PointCloudRendererError::UnknownError => "Unknown Renderer Error".into(),
|
||||
};
|
||||
|
||||
write!(f, "{}", description)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for PointCloudRendererError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
|
||||
// Generic error, no backtrace.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PointCloudRenderer {
|
||||
num_cameras: usize,
|
||||
|
||||
arcball_camera: Arc<Mutex<MouseCameraArcball>>,
|
||||
|
||||
/// The OpenGL program
|
||||
shader_program_id: GLuint,
|
||||
/// The OpenGL vertex shader
|
||||
vertex_shader_id: GLuint,
|
||||
/// The OpenGL fragment shader
|
||||
fragment_shader_id: GLuint,
|
||||
|
||||
// TODO: better matrix types
|
||||
view: [f32; 16],
|
||||
projection: [f32; 16],
|
||||
view_matrix: [[f32; 4]; 4],
|
||||
projection_matrix: [[f32; 4]; 4],
|
||||
/// Matrix applied to each model
|
||||
/// If a model doesn't set its own, it defaults to identity matrix
|
||||
default_model_view_matrix: [[f32; 4]; 4],
|
||||
|
||||
/// Renderer setting: size of the rendered points
|
||||
point_size: u8,
|
||||
/// Renderer setting: shading enabled
|
||||
enable_shading: bool,
|
||||
/// Uniform location in the shader program.
|
||||
enable_shading_index: GLint,
|
||||
|
||||
/// Point array size
|
||||
vertex_arrays_size_bytes: Vec<GLsizei>,
|
||||
|
||||
/// Uniform location in the shader program.
|
||||
view_transform_id: Uniform,
|
||||
/// Uniform location in the shader program.
|
||||
projection_transform_id: Uniform,
|
||||
/// Uniform location in the shader program.
|
||||
model_transform_id: Uniform,
|
||||
|
||||
/// An integer uniform that tells GLSL what type of data it's dealing with.
|
||||
/// This should allow us to mix Point Cloud data and Geometric Objects
|
||||
/// 1 = object
|
||||
/// 2 = pointCloud
|
||||
uniform_vertex_type: Uniform,
|
||||
|
||||
/// Used to signal which camera's point cloud texture is active.
|
||||
uniform_point_cloud_texture_to_use: Uniform,
|
||||
|
||||
/// Uniform for object textures in the shader program.
|
||||
object_texture_uniform: Uniform,
|
||||
/// Location of the texture coordinate input
|
||||
texture_coordinates_attribute: Attribute,
|
||||
|
||||
/// Uniform location in the shader program.
|
||||
point_cloud_texture_indices: Vec<GLint>,
|
||||
/// 'in vec4 inColor'
|
||||
color_vertex_attribute_location: GLint,
|
||||
|
||||
vertex_array_objects: Vec<VertexArray>,
|
||||
vertex_color_buffer_objects: Vec<Buffer>,
|
||||
|
||||
renderable_objects: Vec<PositionableObject>
|
||||
}
|
||||
|
||||
|
||||
impl PointCloudRenderer {
|
||||
|
||||
pub fn new(num_cameras: usize, arcball: Arc<Mutex<MouseCameraArcball>>) -> AnyhowResult<Self> {
|
||||
unsafe {
|
||||
gl::Enable(gl::PROGRAM_POINT_SIZE);
|
||||
}
|
||||
|
||||
let program_id = unsafe { gl::CreateProgram() };
|
||||
|
||||
let mut point_cloud_vertex_shader = read_file_string_contents("src/point_cloud/shaders/point_cloud_vertex_shader.glsl").unwrap();
|
||||
let mut point_cloud_fragment_shader = read_file_string_contents("src/point_cloud/shaders/point_cloud_fragment_shader.glsl").unwrap();
|
||||
|
||||
//let mut point_cloud_vertex_shader = read_file_string_contents("src/point_cloud/shaders/simple_vertex_shader.glsl").unwrap();
|
||||
|
||||
let vertex_shader_id = compile_shader(&point_cloud_vertex_shader, gl::VERTEX_SHADER);
|
||||
let fragment_shader_id = compile_shader(&point_cloud_fragment_shader, gl::FRAGMENT_SHADER);
|
||||
|
||||
link_shader_program(program_id, vertex_shader_id, fragment_shader_id);
|
||||
|
||||
/// Uniform variable name in OpenGL shader program
|
||||
let ENABLE_SHADING : CString = CString::new("enableShading").expect("string is correct");
|
||||
let ENABLE_SHADING_PTR : *const c_char = ENABLE_SHADING.as_ptr() as *const c_char;
|
||||
|
||||
/// Uniform variable name in OpenGL shader program
|
||||
let POINT_CLOUD_0 : CString = CString::new("pointCloudTexture0").expect("string is correct");
|
||||
let POINT_CLOUD_0_PTR : *const c_char = POINT_CLOUD_0.as_ptr() as *const c_char;
|
||||
|
||||
let POINT_CLOUD_1 : CString = CString::new("pointCloudTexture1").expect("string is correct");
|
||||
let POINT_CLOUD_1_PTR : *const c_char = POINT_CLOUD_1.as_ptr() as *const c_char;
|
||||
|
||||
let COLOR_LOCATION : CString = CString::new("inColor").expect("string is correct");
|
||||
let COLOR_LOCATION_PTR : *const c_char = COLOR_LOCATION.as_ptr() as *const c_char;
|
||||
|
||||
let mut point_cloud_texture_indices = Vec::with_capacity(num_cameras);
|
||||
|
||||
let mut view_index = 0;
|
||||
let mut projection_index = 0;
|
||||
let mut enable_shading_index = 0;
|
||||
|
||||
unsafe {
|
||||
// This function returns -1 if name does not correspond to an active uniform variable in
|
||||
// program, if name starts with the reserved prefix "gl_", or if name is associated with an
|
||||
// atomic counter or a named uniform block.
|
||||
// FIXME: THe 'view' and 'projection' uniforms are not binding for some reason.
|
||||
enable_shading_index = gl::GetUniformLocation(program_id, ENABLE_SHADING_PTR);
|
||||
// TODO: This is hardcoded to 2
|
||||
point_cloud_texture_indices.push(gl::GetUniformLocation(program_id, POINT_CLOUD_0_PTR));
|
||||
point_cloud_texture_indices.push(gl::GetUniformLocation(program_id, POINT_CLOUD_1_PTR));
|
||||
}
|
||||
|
||||
let model_transform = Uniform::lookup("model", program_id)?;
|
||||
let view_transform = Uniform::lookup("view", program_id)?;
|
||||
let projection_transform = Uniform::lookup("projection", program_id)?;
|
||||
|
||||
let object_texture_uniform = Uniform::lookup("objTexture", program_id)?;
|
||||
|
||||
let texture_coordinate_attribute = Attribute::lookup("vTextureCoord", program_id)?;
|
||||
|
||||
let uniform_vertex_type = Uniform::lookup("vertexType", program_id)?;
|
||||
|
||||
let uniform_point_cloud_texture_to_use = Uniform::lookup("pointCloudTextureToUse", program_id)?;
|
||||
|
||||
let color_vertex_attribute_location = unsafe {
|
||||
let location = gl::GetAttribLocation(program_id, COLOR_LOCATION_PTR);
|
||||
|
||||
gl::EnableVertexAttribArray(location as u32);
|
||||
|
||||
gl::VertexAttribPointer(
|
||||
location as u32,
|
||||
gl::BGRA as i32,
|
||||
gl::UNSIGNED_BYTE,
|
||||
gl::TRUE,
|
||||
0 as i32,
|
||||
0 as *const c_void,
|
||||
);
|
||||
|
||||
location
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
num_cameras,
|
||||
arcball_camera: arcball,
|
||||
view: initial_view_matrix_4x4_flat(),
|
||||
projection: initial_projection_matrix_4x4_flat(),
|
||||
view_matrix: initial_view_matrix_4x4(),
|
||||
projection_matrix: initial_projection_matrix_4x4(),
|
||||
default_model_view_matrix: identity_matrix_4x4(),
|
||||
shader_program_id: program_id,
|
||||
vertex_shader_id,
|
||||
fragment_shader_id,
|
||||
object_texture_uniform,
|
||||
texture_coordinates_attribute: texture_coordinate_attribute,
|
||||
uniform_vertex_type,
|
||||
uniform_point_cloud_texture_to_use,
|
||||
point_size: 1,
|
||||
enable_shading: false,
|
||||
view_transform_id: view_transform,
|
||||
projection_transform_id: projection_transform,
|
||||
model_transform_id: model_transform,
|
||||
enable_shading_index,
|
||||
point_cloud_texture_indices,
|
||||
vertex_array_objects: Vec::with_capacity(num_cameras),
|
||||
vertex_arrays_size_bytes : Vec::with_capacity(num_cameras),
|
||||
vertex_color_buffer_objects: Vec::with_capacity(num_cameras),
|
||||
color_vertex_attribute_location,
|
||||
renderable_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Run once to set up initial rendering
|
||||
///
|
||||
pub fn setup_rendering(&mut self) -> AnyhowResult<()> {
|
||||
for _ in 0 .. self.num_cameras {
|
||||
let vao = VertexArray::new_initialized();
|
||||
vao.bind();
|
||||
self.vertex_array_objects.push(vao);
|
||||
|
||||
let buffer = Buffer::new_initialized();
|
||||
self.vertex_color_buffer_objects.push(buffer);
|
||||
self.vertex_arrays_size_bytes.push(0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Update the view matrices
|
||||
///
|
||||
pub fn update_view_projection(&mut self, view: [[f32; 4]; 4], _projection: [[f32; 4]; 4]) {
|
||||
self.view_matrix = view;
|
||||
self.projection_matrix = initial_projection_matrix_4x4();
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn update_point_clouds(&mut self,
|
||||
color_images: &Vec<ImageProxy>,
|
||||
point_cloud_textures: &Vec<Texture>) -> Result<()>
|
||||
{
|
||||
for i in 0 .. self.num_cameras {
|
||||
let color_image_bytes = color_images.get(i).unwrap();
|
||||
let color_image_size_bytes = color_image_bytes.len() as i32;
|
||||
|
||||
let vao = self.vertex_array_objects.get(i).unwrap();
|
||||
let vertex_color_buffer = self.vertex_color_buffer_objects.get(i).unwrap();
|
||||
let mut vertex_array_size_bytes = self.vertex_arrays_size_bytes.get_mut(i).unwrap();
|
||||
|
||||
vao.bind();
|
||||
vertex_color_buffer.bind_as_array_buffer();
|
||||
|
||||
//if *vertex_array_size_bytes != color_image_size_bytes {
|
||||
//println!("Establishing buffer");
|
||||
unsafe {
|
||||
gl::BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
color_image_size_bytes as isize,
|
||||
null(),
|
||||
gl::STREAM_DRAW
|
||||
);
|
||||
}
|
||||
*vertex_array_size_bytes = color_image_size_bytes;
|
||||
//}
|
||||
|
||||
let vertex_mapped_buffer = unsafe {
|
||||
gl::MapBufferRange(
|
||||
gl::ARRAY_BUFFER,
|
||||
0,
|
||||
color_image_size_bytes as isize,
|
||||
gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT
|
||||
) as *mut u8
|
||||
};
|
||||
|
||||
// if vertex_mapped_buffer as usize == 0 {
|
||||
// let error = gl_get_error().expect_err("should be map buffer range error");
|
||||
// return Err(PointCloudRendererError::OpenGlError(error));
|
||||
// }
|
||||
|
||||
let result = unsafe {
|
||||
// NB: This is the working texturing:
|
||||
std::ptr::copy_nonoverlapping::<u8>(
|
||||
color_image_bytes.as_ptr(),
|
||||
vertex_mapped_buffer,
|
||||
color_image_size_bytes as usize);
|
||||
|
||||
gl::UnmapBuffer(gl::ARRAY_BUFFER)
|
||||
};
|
||||
|
||||
// if result == gl::FALSE {
|
||||
// let error = gl_get_error().expect_err("should be unmap buffer error");
|
||||
// return Err(PointCloudRendererError::OpenGlError(error));
|
||||
// }
|
||||
|
||||
// THIS IS THE POINT CLOUD
|
||||
let point_cloud_texture = point_cloud_textures.get(i).unwrap();
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, point_cloud_texture.id());
|
||||
gl::BindImageTexture(
|
||||
i as GLuint, // image unit (zero-indexed) // TODO - why does it work this way?
|
||||
point_cloud_texture.id(), // texture
|
||||
0, // level
|
||||
gl::FALSE, // layered
|
||||
0, // layer
|
||||
gl::READ_ONLY, // access
|
||||
gl::RGBA32F, //POINT_CLOUD_TEXTURE_FORMAT,
|
||||
);
|
||||
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
|
||||
gl::EnableVertexAttribArray(self.color_vertex_attribute_location as u32);
|
||||
|
||||
gl::VertexAttribPointer(
|
||||
self.color_vertex_attribute_location as u32,
|
||||
gl::BGRA as i32,
|
||||
gl::UNSIGNED_BYTE,
|
||||
gl::TRUE,
|
||||
0 as i32,
|
||||
0 as *const c_void,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl::BindVertexArray(0);
|
||||
}
|
||||
|
||||
gl_get_error()
|
||||
.map_err(|err| PointCloudRendererError::OpenGlError(err))
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn render(&mut self) -> Result<()> {
|
||||
// Animate the models.
|
||||
for obj in self.renderable_objects.iter_mut() {
|
||||
match obj.object_type {
|
||||
ObjectType::Level => continue,
|
||||
ObjectType::Skybox => continue,
|
||||
_ => {},
|
||||
}
|
||||
obj.increment_rotation_y(0.05); // TODO: Should be based on wall clock instead
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl::UseProgram(self.shader_program_id);
|
||||
|
||||
gl::Enable(gl::DEPTH_TEST);
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
gl::PointSize(self.point_size as f32);
|
||||
|
||||
// Update view/projection matrices in shader
|
||||
let typed_projection = self.projection_matrix.as_ptr() as *const GLfloat;
|
||||
gl::UniformMatrix4fv(self.projection_transform_id.id(), 1, gl::FALSE, typed_projection);
|
||||
|
||||
let typed_view = self.view_matrix.as_ptr() as *const GLfloat;
|
||||
gl::UniformMatrix4fv(self.view_transform_id.id(), 1, gl::FALSE, typed_view);
|
||||
|
||||
// Default model transform
|
||||
let typed_model_view = self.default_model_view_matrix.as_ptr() as *const GLfloat;
|
||||
gl::UniformMatrix4fv(self.model_transform_id.id(), 1, gl::FALSE, typed_model_view);
|
||||
|
||||
// Update render settings in shader
|
||||
let _enable_shading = if self.enable_shading { 1 } else { 0 };
|
||||
let enable_shading = 0; // TODO FIXME FIXME FIXME
|
||||
|
||||
gl::Uniform1i(self.enable_shading_index, enable_shading);
|
||||
|
||||
// Tell GLSL we're doing point clouds
|
||||
gl::Uniform1i(self.uniform_vertex_type.id() as GLint, 2);
|
||||
|
||||
for i in 0 .. self.num_cameras {
|
||||
let vao = self.vertex_array_objects.get(i).unwrap();
|
||||
let vertex_array_size_bytes = self.vertex_arrays_size_bytes.get(i).unwrap();
|
||||
let size = vertex_array_size_bytes / size_of::<BgraPixel>() as i32;
|
||||
|
||||
vao.bind();
|
||||
|
||||
gl::Uniform1i(self.uniform_point_cloud_texture_to_use.id() as GLint, i as GLint);
|
||||
|
||||
gl::DrawArrays(gl::POINTS, 0, size);
|
||||
}
|
||||
|
||||
gl::BindVertexArray(0);
|
||||
|
||||
// Now tell GLSL we're doing objects
|
||||
gl::Uniform1i(self.uniform_vertex_type.id() as GLint, 1);
|
||||
|
||||
for positionable in self.renderable_objects.iter() {
|
||||
positionable.draw(self.model_transform_id);
|
||||
}
|
||||
|
||||
gl_get_error()
|
||||
.map_err(|err| PointCloudRendererError::OpenGlError(err))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_point_size(&mut self, point_size: u8) {
|
||||
self.point_size = point_size;
|
||||
}
|
||||
|
||||
pub fn set_enable_shading(&mut self, enable_shading: bool) {
|
||||
self.enable_shading = enable_shading;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is useful to write solid colors instead of webcam color data
|
||||
// std::ptr::copy::<u8>(color_src, vertex_mapped_buffer as *mut u8, color_image_size_bytes as usize);
|
||||
// std::ptr::write_bytes(vertex_mapped_buffer, 255, color_image_size_bytes as usize);
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
//! 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 anyhow::Result as AnyhowResult;
|
||||
use crate::azure_kinect::sensor_control::CaptureProvider;
|
||||
use crate::core_types::RgbaF32;
|
||||
use crate::gui::mouse_camera_arcball::MouseCameraArcball;
|
||||
use crate::opengl::wrapper::other_misc_wrapper::OpenGlError;
|
||||
use crate::opengl::wrapper::other_misc_wrapper::{Framebuffer, Renderbuffer};
|
||||
use crate::opengl::wrapper::texture::Texture;
|
||||
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
|
||||
use crate::point_cloud::debug::image_proxy::ImageProxy;
|
||||
use crate::point_cloud::gpu_point_cloud_converter::{GpuPointCloudConverter, PointCloudComputeError};
|
||||
use crate::point_cloud::pixel_structs::BgraPixel;
|
||||
use crate::point_cloud::pixel_structs::DepthPixel;
|
||||
use crate::point_cloud::point_cloud_renderer::{PointCloudRenderer, PointCloudRendererError};
|
||||
use crate::point_cloud::util::{colorize_depth_blue_to_red, get_depth_mode_range, ValueRange};
|
||||
use crate::point_cloud::viewer_image::ViewerImage;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::{Image, Calibration, ImageFormat, Capture, Transformation};
|
||||
|
||||
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 for all cameras
|
||||
all_device_calibrations: Vec<Calibration>,
|
||||
|
||||
/// Transformations are constructed from camera calibrations
|
||||
transformations: Vec<Transformation>,
|
||||
|
||||
/// Near and far minima/maxima for the current depth sensor mode.
|
||||
/// These are constructed for each camera from the camera calibrations.
|
||||
expected_depth_value_ranges: Vec<ValueRange>,
|
||||
|
||||
frame_buffer: Framebuffer,
|
||||
depth_buffer: Renderbuffer,
|
||||
|
||||
last_captures: Vec<Option<CaptureProxy>>,
|
||||
|
||||
/// Buffer that holds the depth image transformed to the color coordinate space.
|
||||
/// Used in color mode only.
|
||||
transformed_depth_images: Vec<Option<ImageProxy>>,
|
||||
|
||||
/// 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<ImageProxy>>,
|
||||
|
||||
/// 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<ImageProxy>,
|
||||
depth_xy_tables: Vec<ImageProxy>,
|
||||
|
||||
/// Background clear color for OpenGL.
|
||||
clear_color: RgbaF32,
|
||||
|
||||
/// For debugging
|
||||
/// If present, use these instead of camera captures.
|
||||
debug_static_color_frames: Vec<ImageProxy>,
|
||||
debug_static_depth_frames: Vec<ImageProxy>,
|
||||
}
|
||||
|
||||
// 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,
|
||||
all_device_calibrations: &Vec<Calibration>,
|
||||
enable_color_point_cloud: bool,
|
||||
initial_colorization_strategy: ColorizationStrategy,
|
||||
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 mut expected_depth_value_ranges = Vec::new();
|
||||
let mut transformations = Vec::new();
|
||||
|
||||
for calibration in all_device_calibrations {
|
||||
let value_range = get_depth_mode_range(calibration.0.depth_mode)
|
||||
.expect("Should be in correct depth sensor mode.");
|
||||
expected_depth_value_ranges.push(value_range);
|
||||
|
||||
let transformation = Transformation::from_calibration(&calibration);
|
||||
transformations.push(transformation);
|
||||
}
|
||||
|
||||
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 i 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());
|
||||
|
||||
let calibration = all_device_calibrations.get(i).unwrap();
|
||||
|
||||
// TODO: generate color xytable only on `enable_color_point_cloud`.
|
||||
let color_xy_table = GpuPointCloudConverter::generate_xy_table(
|
||||
&calibration,
|
||||
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_COLOR,
|
||||
).unwrap();
|
||||
|
||||
let depth_xy_table = GpuPointCloudConverter::generate_xy_table(
|
||||
&calibration,
|
||||
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH,
|
||||
).unwrap();
|
||||
|
||||
color_xy_tables.push(ImageProxy::consume_k4a_image(color_xy_table));
|
||||
depth_xy_tables.push(ImageProxy::consume_k4a_image(depth_xy_table));
|
||||
}
|
||||
|
||||
let mut debug_static_color_frames = Vec::new();
|
||||
//debug_static_color_frames.push(ImageProxy::from_file("output/color_src_0", 0, 0, 0, ImageFormat::ColorBgra32).unwrap());
|
||||
//debug_static_color_frames.push(ImageProxy::from_file("output/color_src_1", 0, 0, 0, ImageFormat::ColorBgra32).unwrap());
|
||||
|
||||
let mut debug_static_depth_frames = Vec::new();
|
||||
//debug_static_depth_frames.push(ImageProxy::from_file("output/depth_src_0", 3840, 2160, 7680, ImageFormat::Depth16).unwrap());
|
||||
//debug_static_depth_frames.push(ImageProxy::from_file("output/depth_src_1", 3840, 2160, 7680, ImageFormat::Depth16).unwrap());
|
||||
|
||||
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()).expect("should build"),
|
||||
point_cloud_converters,
|
||||
frame_buffer: Framebuffer::new_initialized(),
|
||||
depth_buffer,
|
||||
color_xy_tables,
|
||||
depth_xy_tables,
|
||||
colorization_strategy: initial_colorization_strategy,
|
||||
transformations,
|
||||
last_captures,
|
||||
transformed_depth_images,
|
||||
point_cloud_colorizations,
|
||||
xyz_textures,
|
||||
expected_depth_value_ranges,
|
||||
clear_color,
|
||||
debug_static_color_frames,
|
||||
debug_static_depth_frames,
|
||||
all_device_calibrations: all_device_calibrations.clone(),
|
||||
};
|
||||
|
||||
visualizer.set_colorization_strategy(initial_colorization_strategy).expect("Should work");
|
||||
|
||||
visualizer
|
||||
}
|
||||
|
||||
/// Setup initial rendering
|
||||
///
|
||||
pub fn setup_rendering(&mut self) -> AnyhowResult<()> {
|
||||
self.point_cloud_renderer.setup_rendering()
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
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<CaptureProxy>) -> 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<CaptureProxy>) -> 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<CaptureProxy>) -> 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)?;
|
||||
}
|
||||
|
||||
let mut color_captures = Vec::new();
|
||||
|
||||
let color_frames = if self.debug_static_color_frames.len() > 0 {
|
||||
&self.debug_static_color_frames
|
||||
} else {
|
||||
// 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<ImageProxy> = self.point_cloud_colorizations.iter()
|
||||
.map(|img| img.as_ref().map(|img| img.clone()).unwrap()) // Potential ref leak #1
|
||||
.collect();
|
||||
|
||||
color_captures = colorizations;
|
||||
&color_captures
|
||||
};
|
||||
|
||||
self.point_cloud_renderer.update_point_clouds(
|
||||
&color_frames,
|
||||
&self.xyz_textures
|
||||
).map_err(|err| PointCloudVisualizerError::PointCloudRendererError(err))
|
||||
}
|
||||
|
||||
fn update_point_clouds_for_camera(&mut self, camera_index: usize, capture: CaptureProxy) -> Result<()> {
|
||||
//
|
||||
// Depth Image extractor
|
||||
//
|
||||
|
||||
let transformation = self.transformations.get(camera_index).unwrap();
|
||||
|
||||
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 {
|
||||
// TODO: TEMP SUPPORT MULTI-CAMERA
|
||||
if let Some(transformed_depth_image) = self.transformed_depth_images.get_mut(camera_index)
|
||||
.unwrap()
|
||||
.as_mut() {
|
||||
unsafe {
|
||||
|
||||
let result = k4a_sys::k4a_transformation_depth_image_to_color_camera(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Convert depth image to depth texture
|
||||
//
|
||||
|
||||
let use_depth_image = self.debug_static_depth_frames.get(camera_index)
|
||||
.unwrap_or(&depth_image);
|
||||
|
||||
let result = self.point_cloud_converters.get(camera_index).unwrap().convert(
|
||||
&use_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.clone());
|
||||
}
|
||||
|
||||
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.clone());
|
||||
}
|
||||
|
||||
} else {
|
||||
// This creates a color spectrum based on depth.
|
||||
let dst_length = use_depth_image.get_width_pixels() * use_depth_image.get_height_pixels();
|
||||
|
||||
let depth_value_range = self.expected_depth_value_ranges.get(camera_index).unwrap();
|
||||
|
||||
unsafe {
|
||||
// src: DepthPixel
|
||||
let src_pixel_buffer = use_depth_image.get_buffer();
|
||||
let typed_src_pixel_buffer = src_pixel_buffer as *const DepthPixel;
|
||||
|
||||
// TODO TEMP MULTI-CAMERA SUPPORT
|
||||
// dst: BgraPixel
|
||||
let dst_pixel_buffer = self.point_cloud_colorizations.get_mut(camera_index)
|
||||
.unwrap()
|
||||
.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, 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);
|
||||
}
|
||||
|
||||
let calibration = self.all_device_calibrations.get(camera_index).unwrap();
|
||||
|
||||
self.colorization_strategy = strategy;
|
||||
self.point_cloud_renderer.set_enable_shading(strategy == ColorizationStrategy::Shaded);
|
||||
|
||||
if strategy == ColorizationStrategy::Color {
|
||||
let width = calibration.0.color_camera_calibration.resolution_width;
|
||||
let height = calibration.0.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(ImageProxy::consume_k4a_image(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 = calibration.0.depth_camera_calibration.resolution_width as u32;
|
||||
let height = calibration.0.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(ImageProxy::consume_k4a_image(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() {
|
||||
let xy_table = self.depth_xy_tables.get(camera_index).unwrap();
|
||||
point_cloud_converter.set_active_xy_table(&xy_table)?;
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// This is taken from Microsoft's MIT-licensed k4a libraries.
|
||||
// From the file `tools/k4aviewer/graphics/shaders/gpudepthtopointcloudconverter.cpp`
|
||||
#version 430
|
||||
|
||||
layout(location=0, rgba32f) writeonly uniform image2D destTex;
|
||||
|
||||
layout(location=1, r16ui) readonly uniform uimage2D depthImage;
|
||||
layout(location=2, rg32f) readonly uniform image2D xyTable;
|
||||
|
||||
layout(local_size_x = 1, local_size_y = 1) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
float vertexValue = float(imageLoad(depthImage, pixel));
|
||||
// TODO: The problem is that these are bound to (0.0, 0.0). It isn't working.
|
||||
vec2 xyValue = imageLoad(xyTable, pixel).xy;
|
||||
|
||||
float alpha = 1.0f;
|
||||
// vertexPosition format: RGB colors as floats, but not range [0.0, 1.0], but rather [0.0, 255.0]
|
||||
// HOWEVER, there are operations that reverse the sign of the 'x' channel ('r') and scale down by 1000 below.
|
||||
vec3 vertexPosition = vec3(vertexValue * xyValue.x, vertexValue * xyValue.y, vertexValue);
|
||||
|
||||
// Invalid pixels have their XY table values set to 0.
|
||||
// Set the rest of their values to 0 so clients can pick them out.
|
||||
//
|
||||
if (xyValue.x == 0.0f && xyValue.y == 0.0f)
|
||||
{
|
||||
alpha = 0.0f;
|
||||
vertexValue = 0.0f;
|
||||
}
|
||||
|
||||
// Vertex positions are in millimeters, but everything else is in meters, so we need to convert
|
||||
//
|
||||
vertexPosition /= 1000.0f;
|
||||
|
||||
// OpenGL and K4A have different conventions on which direction is positive -
|
||||
// we need to flip the X coordinate.
|
||||
//
|
||||
vertexPosition.x *= -1;
|
||||
|
||||
// Distance Threshold
|
||||
// TODO: Make configurable.
|
||||
/*if (vertexPosition.z > 1.5) {
|
||||
alpha = 0.0f;
|
||||
vertexPosition.x = 0.0;
|
||||
vertexPosition.y = 0.0;
|
||||
vertexPosition.z = 0.0;
|
||||
}*/
|
||||
|
||||
imageStore(destTex, pixel, vec4(vertexPosition, alpha));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// This is taken from Microsoft's MIT-licensed k4a libraries.
|
||||
// From the file `tools/k4aviewer/graphics/shaders/k4apointcloudshaders.h`
|
||||
#version 430
|
||||
|
||||
in vec4 vertexColor;
|
||||
out vec4 fragmentColor;
|
||||
|
||||
in vec2 texCoord;
|
||||
uniform bool enableShading;
|
||||
|
||||
uniform int vertexType;
|
||||
|
||||
uniform sampler2D objTexture; // C++ method
|
||||
//layout(location=2, rgba32f) readonly uniform image2D objTexture; // k4a method
|
||||
|
||||
void main()
|
||||
{
|
||||
if (vertexColor.a == 0.0f)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
if (vertexType == 1)
|
||||
{
|
||||
// Object mode
|
||||
vec2 coord = texCoord;
|
||||
fragmentColor = texture2D(objTexture, coord);
|
||||
}
|
||||
else if (vertexType == 2)
|
||||
{
|
||||
// Point cloud mode
|
||||
fragmentColor = vertexColor;
|
||||
}
|
||||
|
||||
if (fragmentColor.a == 0.0f)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// This is taken from Microsoft's MIT-licensed k4a libraries.
|
||||
// From the file `tools/k4aviewer/graphics/shaders/k4apointcloudshaders.h`
|
||||
#version 430
|
||||
|
||||
// NB: It appears that locations need to be wide enough apart, or the colors bleed across locations.
|
||||
layout(location=0) in vec4 inColor;
|
||||
//layout(location=10) in vec4 inColor1; // TODO: Get rid of this!
|
||||
|
||||
// For use with objects.
|
||||
// Might restructure the entire program.
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
|
||||
in vec2 vTextureCoord;
|
||||
out vec2 texCoord;
|
||||
|
||||
out vec4 vertexColor;
|
||||
|
||||
// Transformation matrices
|
||||
uniform mat4 model;
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
|
||||
// 1 = object, 2 = pointCloud
|
||||
uniform int vertexType;
|
||||
|
||||
// NB: This is not a scalable approach. But it might work for now.
|
||||
// 0 = camera0, 1 = camera1
|
||||
uniform int pointCloudTextureToUse;
|
||||
|
||||
layout(rgba32f, binding=0) readonly uniform image2D pointCloudTexture0;
|
||||
layout(rgba32f, binding=1) readonly uniform image2D pointCloudTexture1;
|
||||
|
||||
uniform bool enableShading;
|
||||
|
||||
// bool GetPoint3d(in vec2 pointCloudSize, in ivec2 point2d, out vec3 point3d)
|
||||
// {
|
||||
// if (point2d.x < 0 || point2d.x >= pointCloudSize.x ||
|
||||
// point2d.y < 0 || point3d.y >= pointCloudSize.y)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// point3d = imageLoad(pointCloudTexture0, point2d).xyz;
|
||||
// if (point3d.z <= 0)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 pointCloudSize0 = imageSize(pointCloudTexture0);
|
||||
int pointCloudLength0 = pointCloudSize0.x * pointCloudSize0.y;
|
||||
|
||||
vec3 vertexPosition;
|
||||
vec4 colorOut;
|
||||
|
||||
// We're having to multiplex on gl_VertexID, and I don't think we get double the range for two
|
||||
// cameras. This effectively 'downsamples' each camera. We need to find a way to double this.
|
||||
// I hate drawing like this
|
||||
//if (gl_VertexID < pointCloudLength0) { // TODO - this is the one to enable
|
||||
if (gl_VertexID < 10) {
|
||||
// Camera #0
|
||||
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize0.x, gl_VertexID / pointCloudSize0.x);
|
||||
|
||||
vertexPosition = imageLoad(pointCloudTexture0, currentDepthPixelCoordinates).xyz;
|
||||
|
||||
} else {
|
||||
// Camera #1
|
||||
ivec2 pointCloudSize1 = imageSize(pointCloudTexture1);
|
||||
int pointCloudLength1 = pointCloudSize1.x * pointCloudSize1.y;
|
||||
|
||||
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize1.x, gl_VertexID / pointCloudSize1.x);
|
||||
|
||||
// // Let's move the model away a bit.
|
||||
// vec3 originalPosition = imageLoad(pointCloudTexture1, currentDepthPixelCoordinates).xyz;
|
||||
// vertexPosition = vec3(
|
||||
// originalPosition.x - 1.5,
|
||||
// originalPosition.y,
|
||||
// originalPosition.z
|
||||
// );
|
||||
|
||||
vertexPosition = imageLoad(pointCloudTexture1, currentDepthPixelCoordinates).xyz;
|
||||
|
||||
vertexPosition = position;
|
||||
}
|
||||
|
||||
if (view[0][0] > 0.5) {
|
||||
vertexPosition.x -= 2.0;
|
||||
vertexPosition.y -= 2.0;
|
||||
}
|
||||
|
||||
vertexColor = colorOut;
|
||||
texCoord = vTextureCoord;
|
||||
|
||||
if (vertexType == 1) {
|
||||
// Object mode
|
||||
|
||||
vertexColor = colorOut;
|
||||
texCoord = vTextureCoord;
|
||||
|
||||
} else if (vertexType == 2) {
|
||||
// Point cloud mode
|
||||
|
||||
if (pointCloudTextureToUse == 0) {
|
||||
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize0.x, gl_VertexID / pointCloudSize0.x);
|
||||
|
||||
vertexPosition = imageLoad(pointCloudTexture0, currentDepthPixelCoordinates).xyz;
|
||||
|
||||
vertexPosition.x += 1.0; // Camera 0 looks best on the left
|
||||
|
||||
colorOut = vec4(
|
||||
inColor.r,
|
||||
inColor.g,
|
||||
inColor.b,
|
||||
255
|
||||
);
|
||||
|
||||
} else if (pointCloudTextureToUse == 1) {
|
||||
ivec2 pointCloudSize1 = imageSize(pointCloudTexture1);
|
||||
int pointCloudLength1 = pointCloudSize1.x * pointCloudSize1.y;
|
||||
|
||||
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize1.x, gl_VertexID / pointCloudSize1.x);
|
||||
|
||||
vertexPosition = imageLoad(pointCloudTexture1, currentDepthPixelCoordinates).xyz;
|
||||
|
||||
vertexPosition.x -= 1.0; // Camera 1 looks best on the right
|
||||
|
||||
colorOut = vec4(
|
||||
inColor.r,
|
||||
inColor.g,
|
||||
inColor.b,
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
if (vertexPosition.z > 1.5) {
|
||||
// Depth threshold
|
||||
// This leads directly to pixel discard
|
||||
colorOut.a = 0.0;
|
||||
}
|
||||
|
||||
vertexColor = colorOut;
|
||||
}
|
||||
|
||||
gl_Position = projection * view * model * vec4(vertexPosition, 1);
|
||||
|
||||
// Pass along the 'invalid pixel' flag as the alpha channel
|
||||
//
|
||||
// if (vertexPosition.z == 0.0f)
|
||||
// {
|
||||
// vertexColor.a = 0.0f;
|
||||
// }
|
||||
|
||||
// NB: This is affecting the second camera. Disabling for now.
|
||||
// if (enableShading)
|
||||
// {
|
||||
// // Compute the location of the closest neighbor pixel to compute lighting
|
||||
// //
|
||||
// vec3 closestNeighbor = vertexPosition;
|
||||
//
|
||||
// // If no neighbors have data, default to 1 meter behind point.
|
||||
// //
|
||||
// closestNeighbor.z += 1.0f;
|
||||
//
|
||||
// vec3 outPoint;
|
||||
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates - ivec2(1, 0), outPoint))
|
||||
// {
|
||||
// if (closestNeighbor.z > outPoint.z)
|
||||
// {
|
||||
// closestNeighbor = outPoint;
|
||||
// }
|
||||
// }
|
||||
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates + ivec2(1, 0), outPoint))
|
||||
// {
|
||||
// if (closestNeighbor.z > outPoint.z)
|
||||
// {
|
||||
// closestNeighbor = outPoint;
|
||||
// }
|
||||
// }
|
||||
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates - ivec2(0, 1), outPoint))
|
||||
// {
|
||||
// if (closestNeighbor.z > outPoint.z)
|
||||
// {
|
||||
// closestNeighbor = outPoint;
|
||||
// }
|
||||
// }
|
||||
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates + ivec2(0, 1), outPoint))
|
||||
// {
|
||||
// if (closestNeighbor.z > outPoint.z)
|
||||
// {
|
||||
// closestNeighbor = outPoint;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// vec3 lightPosition = vec3(0, 0, 0);
|
||||
// float occlusion = length(vertexPosition - closestNeighbor) * 20.0f;
|
||||
// float diffuse = 1.0f - clamp(occlusion, 0.0f, 0.6f);
|
||||
//
|
||||
// float distance = length(lightPosition - vertexPosition);
|
||||
//
|
||||
// // Attenuation term for light source that covers distance up to 50 meters
|
||||
// // http://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation
|
||||
// //
|
||||
// float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance);
|
||||
//
|
||||
// vertexColor = vec4(attenuation * diffuse * vertexColor.rgb, vertexColor.a);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//#version 140
|
||||
#version 420
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
in vec4 inColor; // UNUSED
|
||||
|
||||
layout(rgba32f, binding=0) readonly uniform image2D pointCloudTexture0; // UNUSED
|
||||
layout(rgba32f, binding=1) readonly uniform image2D pointCloudTexture1; // UNUSED
|
||||
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
|
||||
uniform bool enableShading; // UNUSED
|
||||
|
||||
out vec3 v_position;
|
||||
//out vec3 v_normal;
|
||||
|
||||
void main() {
|
||||
v_position = position;
|
||||
//v_normal = normal;
|
||||
gl_Position = projection * view * vec4(v_position * 0.005, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use crate::point_cloud::pixel_structs::{DepthPixel, BgraPixel};
|
||||
use imgui::sys::igColorConvertHSVtoRGB;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
|
||||
/// Ranges of values we can expect from the depth sensor, depending on its current configuration.
|
||||
pub struct ValueRange {
|
||||
/// Lowest (nearest) value reported from the depth sensor in millimeters.
|
||||
pub near: DepthPixel,
|
||||
/// Highest (furthest) value reported from the depth sensor in millimeters.
|
||||
pub far: DepthPixel,
|
||||
}
|
||||
|
||||
/// Gets the range of values that we expect to see from the depth camera
|
||||
/// when using a given depth mode, in millimeters
|
||||
pub fn get_depth_mode_range(depth_mode: k4a_sys::k4a_depth_mode_t) -> Option<ValueRange> {
|
||||
match depth_mode {
|
||||
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_2X2BINNED => Some(ValueRange { near: 500, far: 5800 }),
|
||||
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED => Some(ValueRange { near: 500, far: 4000 }),
|
||||
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED => Some(ValueRange { near: 250, far: 3000 }),
|
||||
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_UNBINNED => Some(ValueRange { near: 250, far: 2500 }),
|
||||
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_PASSIVE_IR => None, // TODO: Return error
|
||||
_ => None, // TODO: Return error
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a port of Microsoft's `ColorizeBlueToRed` in the open source / MIT-licensed libk4a
|
||||
pub fn colorize_depth_blue_to_red(depth_pixel: DepthPixel, value_range: &ValueRange) -> BgraPixel {
|
||||
let pixel_max = libc::uint8_t::max_value();
|
||||
|
||||
let mut result = BgraPixel { blue: 0, green: 0, red: 0, alpha: pixel_max };
|
||||
|
||||
if depth_pixel == 0 {
|
||||
// If the pixel is actually zero and not just below the min value, make it black.
|
||||
return result;
|
||||
}
|
||||
|
||||
let clamped_value = depth_pixel
|
||||
.min(value_range.far)
|
||||
.max(value_range.near);
|
||||
|
||||
// Normalize to [0, 1]
|
||||
let mut hue = (clamped_value - value_range.near) as f32 / (value_range.far - value_range.near) as f32;
|
||||
|
||||
// The 'hue' coordinate in HSV is a polar coordinate, so it 'wraps'.
|
||||
// Purple starts after blue and is close enough to red to be a bit unclear,
|
||||
// so we want to go from blue to red. Purple starts around .6666667,
|
||||
// so we want to normalize to [0, .6666667].
|
||||
let range = 2.0f32 / 3.0;
|
||||
hue *= range;
|
||||
|
||||
// We want blue to be close and red to be far, so we need to reflect the
|
||||
// hue across the middle of the range.
|
||||
hue = range - hue;
|
||||
|
||||
let mut red = 0.0f32;
|
||||
let mut green= 0.0f32;
|
||||
let mut blue = 0.0f32;
|
||||
|
||||
unsafe {
|
||||
igColorConvertHSVtoRGB(hue, 1.0, 1.0, &mut red, &mut green, &mut blue);
|
||||
}
|
||||
|
||||
let pixel_max = pixel_max as f32;
|
||||
result.red = (red * pixel_max) as u8;
|
||||
result.green = (green * pixel_max) as u8;
|
||||
result.blue = (blue * pixel_max) as u8;
|
||||
|
||||
result
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
use std::fmt::Formatter;
|
||||
use std::ptr::null;
|
||||
|
||||
use gl;
|
||||
use gl::types::*;
|
||||
use libc::uint8_t;
|
||||
|
||||
use crate::opengl::wrapper::buffer::Buffer;
|
||||
use crate::opengl::wrapper::other_misc_wrapper::OpenGlError;
|
||||
use crate::opengl::wrapper::other_misc_wrapper::gl_get_error;
|
||||
use crate::opengl::wrapper::texture::Texture;
|
||||
use crate::point_cloud::pixel_structs::BgraPixel;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ViewerImageError>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ViewerImageError {
|
||||
OpenGlError(OpenGlError),
|
||||
}
|
||||
|
||||
impl From<OpenGlError> for ViewerImageError {
|
||||
fn from(error: OpenGlError) -> Self {
|
||||
ViewerImageError::OpenGlError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ViewerImageError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let description = match self {
|
||||
ViewerImageError::OpenGlError(inner) => {
|
||||
format!("Visualizer OpenGL error: {}", inner)
|
||||
},
|
||||
};
|
||||
write!(f, "{}", description)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ViewerImageError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
|
||||
// Generic error, no backtrace.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageDimensions {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ViewerImage {
|
||||
dimensions: ImageDimensions,
|
||||
format: GLenum,
|
||||
|
||||
texture: Texture,
|
||||
texture_buffer: Buffer,
|
||||
|
||||
texture_buffer_size: GLuint,
|
||||
}
|
||||
|
||||
// GLenum format = GL_BGRA (default), but overridden as gl::RGBA
|
||||
// GLenum internalFormat = GL_RGBA8 (default)
|
||||
impl ViewerImage {
|
||||
/// private ctor
|
||||
fn new(
|
||||
dimensions: ImageDimensions,
|
||||
format: GLenum
|
||||
) -> Result<Self> {
|
||||
let texture_buffer_size = (dimensions.width * dimensions.height) as GLuint
|
||||
* get_format_pixel_element_count(format);
|
||||
|
||||
let texture = Texture::new_initialized();
|
||||
let texture_buffer = Buffer::new_initialized();
|
||||
|
||||
unsafe {
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, texture_buffer.id());
|
||||
gl::BufferData(gl::PIXEL_UNPACK_BUFFER, texture_buffer_size as isize, null(), gl::STREAM_DRAW);
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
dimensions,
|
||||
format,
|
||||
texture_buffer_size,
|
||||
texture,
|
||||
texture_buffer,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<Self>
|
||||
{
|
||||
let dimensions = ImageDimensions { width, height };
|
||||
let format = gl::RGBA;
|
||||
let mut viewer_image = ViewerImage::new(dimensions, format)?;
|
||||
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, viewer_image.texture.id());
|
||||
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
|
||||
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
gl::TexStorage2D(
|
||||
gl::TEXTURE_2D,
|
||||
1,
|
||||
gl::RGBA8,
|
||||
width as i32,
|
||||
height as i32,
|
||||
);
|
||||
|
||||
viewer_image.update_texture(None)?;
|
||||
}
|
||||
|
||||
Ok(viewer_image)
|
||||
}
|
||||
|
||||
pub fn texture_id(&self) -> GLuint {
|
||||
self.texture.id()
|
||||
}
|
||||
|
||||
pub unsafe fn update_texture(&mut self, data: Option<&[uint8_t]>) -> Result<()> {
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.texture_buffer.id());
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.texture.id());
|
||||
|
||||
let _cleanup_guard = CleanupGuard {};
|
||||
|
||||
let buffer = gl::MapBufferRange(
|
||||
gl::PIXEL_UNPACK_BUFFER,
|
||||
0,
|
||||
self.texture_buffer_size as isize,
|
||||
gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT,
|
||||
) as *mut BgraPixel; // TODO: It's actually RGBA, not BGRA.
|
||||
|
||||
if buffer as usize == 0 {
|
||||
return Err(ViewerImageError::OpenGlError(gl_get_error().err().unwrap()));
|
||||
}
|
||||
|
||||
// TODO COPY DATA
|
||||
match data {
|
||||
Some(_) => {
|
||||
unreachable!("TODO")
|
||||
},
|
||||
None => {
|
||||
unsafe {
|
||||
*buffer = std::mem::zeroed();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let result = gl::UnmapBuffer(gl::PIXEL_UNPACK_BUFFER);
|
||||
|
||||
if result == 0 {
|
||||
return Err(ViewerImageError::OpenGlError(gl_get_error().err().unwrap()));
|
||||
}
|
||||
|
||||
gl::TexSubImage2D(
|
||||
gl::TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
self.dimensions.width as i32,
|
||||
self.dimensions.height as i32,
|
||||
self.format,
|
||||
gl::UNSIGNED_BYTE,
|
||||
null(),
|
||||
);
|
||||
|
||||
gl_get_error()
|
||||
.map_err(|err| ViewerImageError::OpenGlError(err))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_format_pixel_element_count(format: GLenum) -> GLuint {
|
||||
match format {
|
||||
gl::RED => 1,
|
||||
gl::RG => 2,
|
||||
gl::RGB | gl::BGR => 3,
|
||||
gl::RGBA | gl::BGRA => 4,
|
||||
_ => unreachable!("bad get_format_pixel_element_count") // TODO: Ugh.
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Dedup
|
||||
struct CleanupGuard {}
|
||||
|
||||
impl Drop for CleanupGuard {
|
||||
fn drop(&mut self) {
|
||||
println!("Running CleanupGuard");
|
||||
unsafe {
|
||||
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
use crate::opengl::wrapper::other_misc_wrapper::{gl_get_error, OpenGlError};
|
||||
use gl::types::*;
|
||||
use gl;
|
||||
use std::ffi::c_void;
|
||||
use std::fmt::Formatter;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::{fmt, io};
|
||||
|
||||
/// WebcamWriter uses Webcamoid's akvcam kernel driver to stream output to a /dev/video*
|
||||
/// file. Their wiki contains all of the setup instructions, and I'm checked in some example
|
||||
/// configs into this repo.
|
||||
///
|
||||
/// https://github.com/webcamoid/akvcam
|
||||
pub struct WebcamWriter {
|
||||
/// Internal byte buffer to pull from OpenGL texture and flush to file.
|
||||
buffer: Vec<u8>,
|
||||
buffer_size: usize,
|
||||
/// File to flush raw RGB24 bytes to.
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl WebcamWriter {
|
||||
|
||||
/// Create a WebcamWriter that outputs to the supplied file.
|
||||
/// The file handle is created and owned by WebcamWriter.
|
||||
pub fn open_file(filename: &str, width: usize, height: usize, bit_depth: usize)
|
||||
-> Result<Self, io::Error>
|
||||
{
|
||||
let path = Path::new(filename);
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(path)?;
|
||||
|
||||
let buffer_size = width * height * bit_depth;
|
||||
|
||||
info!("Webcam writer buffer size: {} (width: {}, height: {}, bit depth: {}",
|
||||
buffer_size, width, height, bit_depth);
|
||||
|
||||
let mut buffer = Vec::with_capacity(buffer_size);
|
||||
buffer.resize(buffer_size, 0);
|
||||
|
||||
Ok(Self {
|
||||
buffer,
|
||||
buffer_size,
|
||||
file,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Make it easy to update for new textures of different sizes (dynamic buffer resize)
|
||||
/// Grab the current texture held by `texture_id` and immediately write it to file.
|
||||
pub fn write_current_frame_to_file(&mut self, texture_id: GLuint) -> Result<(), WebcamError> {
|
||||
let typed_buffer = self.buffer.as_mut_ptr() as *mut c_void;
|
||||
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, texture_id);
|
||||
gl::GetnTexImage(gl::TEXTURE_2D, 0, gl::RGB, gl::UNSIGNED_BYTE, self.buffer_size as i32, typed_buffer);
|
||||
|
||||
gl_get_error()?;
|
||||
|
||||
gl::BindTexture(gl::TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
self.file.write_all(&self.buffer)?;
|
||||
self.file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors from this module.
|
||||
#[derive(Debug)]
|
||||
pub enum WebcamError {
|
||||
IoError(io::Error),
|
||||
OpenGlError(OpenGlError),
|
||||
}
|
||||
|
||||
impl fmt::Display for WebcamError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let explain = match self {
|
||||
WebcamError::IoError(e) => format!("IoError: {:?}", e),
|
||||
WebcamError::OpenGlError(e) => format!("OpenGL error: {:?}", e),
|
||||
};
|
||||
write!(f, "{}", explain)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for WebcamError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
|
||||
None // Generic error, no backtrace.
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for WebcamError {
|
||||
fn from(error: io::Error) -> Self {
|
||||
WebcamError::IoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OpenGlError> for WebcamError {
|
||||
fn from(error: OpenGlError) -> Self {
|
||||
WebcamError::OpenGlError(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
use std::time::{UNIX_EPOCH, SystemTime};
|
||||
use rand::distributions::Uniform;
|
||||
use rand::Rng;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
White,
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
Yellow, // Red + Green
|
||||
Teal, // Green + Blue
|
||||
Purple, // Red + Blue
|
||||
Custom { r: u8, g: u8, b: u8, a: u8 },
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn get_time_based_color() -> Self {
|
||||
let start = SystemTime::now();
|
||||
let timestamp = start
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time should work.");
|
||||
|
||||
let seconds = timestamp.as_secs();
|
||||
|
||||
match seconds % 10 {
|
||||
0 | 1 => Color::Purple, //Color::Black,
|
||||
2 | 3 => Color::Blue,
|
||||
4 | 5 => Color::Red,
|
||||
6 | 7 => Color::Green,
|
||||
8 | 9 => Color::White,
|
||||
_ => Color::White,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_random_rgb_color() -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let range = Uniform::new_inclusive(0u8, 255u8);
|
||||
|
||||
let r = rng.sample(&range);
|
||||
let g = rng.sample(&range);
|
||||
let b = rng.sample(&range);
|
||||
|
||||
Color::Custom { r, g, b, a: 255 }
|
||||
}
|
||||
|
||||
pub fn get_u8_r(&self) -> u8 {
|
||||
match &self {
|
||||
Color::Black => 0,
|
||||
Color::White => 255,
|
||||
Color::Red => 255,
|
||||
Color::Green => 0,
|
||||
Color::Blue => 0,
|
||||
Color::Yellow => 255,
|
||||
Color::Teal => 0,
|
||||
Color::Purple => 255,
|
||||
Color::Custom {r, g, b, a} => *r,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_u8_g(&self) -> u8 {
|
||||
match &self {
|
||||
Color::Black => 0,
|
||||
Color::White => 255,
|
||||
Color::Red => 0,
|
||||
Color::Green => 255,
|
||||
Color::Blue => 0,
|
||||
Color::Yellow => 255,
|
||||
Color::Teal => 255,
|
||||
Color::Purple => 0,
|
||||
Color::Custom {r, g, b, a} => *g,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_u8_b(&self) -> u8 {
|
||||
match &self {
|
||||
Color::Black => 0,
|
||||
Color::White => 255,
|
||||
Color::Red => 0,
|
||||
Color::Green => 0,
|
||||
Color::Blue => 255,
|
||||
Color::Yellow => 0,
|
||||
Color::Teal => 255,
|
||||
Color::Purple => 255,
|
||||
Color::Custom {r, g, b, a} => *b,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_u8_a(&self) -> u8 {
|
||||
match &self {
|
||||
Color::Black => 255,
|
||||
Color::White => 255,
|
||||
Color::Red => 255,
|
||||
Color::Green => 255,
|
||||
Color::Blue => 255,
|
||||
Color::Yellow => 255,
|
||||
Color::Teal => 255,
|
||||
Color::Purple => 255,
|
||||
Color::Custom {r, g, b, a} => *a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
use anyhow::anyhow;
|
||||
use cgmath::num_traits::Float;
|
||||
use crate::AnyhowResult;
|
||||
use crate::zeromq::color::Color;
|
||||
use crate::zeromq::point::Point;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::Image;
|
||||
use kinect::ImageFormat;
|
||||
use std::mem::size_of;
|
||||
|
||||
pub struct PointCloudResult {
|
||||
pub point_cloud_image: Image,
|
||||
pub point_count: u32,
|
||||
}
|
||||
|
||||
/// This has been adapted from libk4a sources (MIT-licensed).
|
||||
pub fn calculate_point_cloud(depth_image: &Image, xy_table_image: &Image) -> AnyhowResult<PointCloudResult> {
|
||||
let width = depth_image.get_width_pixels();
|
||||
let height = depth_image.get_height_pixels();
|
||||
|
||||
let point_cloud_image= Image::create(
|
||||
ImageFormat::Custom,
|
||||
width as u32,
|
||||
height as u32,
|
||||
width as u32 * size_of::<k4a_sys::k4a_float3_t>() as u32,
|
||||
)?;
|
||||
|
||||
let point_cloud_data = point_cloud_image.get_buffer() as *mut k4a_sys::k4a_float3_t;
|
||||
let xy_table_data = xy_table_image.get_buffer() as *mut k4a_sys::k4a_float2_t;
|
||||
let depth_data = depth_image.get_buffer() as *mut u16; // uint16_t
|
||||
|
||||
let depth_data_length = (width * height) as isize;
|
||||
|
||||
let mut point_count = 0;
|
||||
|
||||
for i in 0 .. depth_data_length {
|
||||
unsafe {
|
||||
// TODO: This is missing `isnan` checks.
|
||||
// if (depth_data[i] != 0 && !isnan(xy_table_data[i].xy.x) && !isnan(xy_table_data[i].xy.y))
|
||||
if (*depth_data.offset(i)) != 0 {
|
||||
(*point_cloud_data.offset(i)).xyz.x = (*xy_table_data.offset(i)).xy.x * ((*depth_data.offset(i)) as f32);
|
||||
(*point_cloud_data.offset(i)).xyz.y = (*xy_table_data.offset(i)).xy.y * ((*depth_data.offset(i)) as f32);
|
||||
(*point_cloud_data.offset(i)).xyz.z = (*depth_data.offset(i)) as f32;
|
||||
point_count += 1;
|
||||
} else {
|
||||
(*point_cloud_data.offset(i)).xyz.x = f32::nan();
|
||||
(*point_cloud_data.offset(i)).xyz.y = f32::nan();
|
||||
(*point_cloud_data.offset(i)).xyz.z = f32::nan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PointCloudResult {
|
||||
point_cloud_image,
|
||||
point_count,
|
||||
})
|
||||
}
|
||||
|
||||
// Directly return as a vector.
|
||||
pub fn calculate_point_cloud2(depth_image: &Image, xy_table_image: &Image, color: Color) -> AnyhowResult<Vec<Point>> {
|
||||
let width = depth_image.get_width_pixels();
|
||||
let height = depth_image.get_height_pixels();
|
||||
|
||||
{
|
||||
let xy_width = xy_table_image.get_width_pixels();
|
||||
let xy_height = xy_table_image.get_height_pixels();
|
||||
if width != xy_width || height != xy_height {
|
||||
return Err(anyhow!("Depth image ({}x{}) and XY table ({}x{}) dimensions are not equal!",
|
||||
width, height, xy_width, xy_height));
|
||||
}
|
||||
}
|
||||
|
||||
let xy_table_data = xy_table_image.get_buffer() as *mut k4a_sys::k4a_float2_t;
|
||||
let depth_data = depth_image.get_buffer() as *mut u16; // uint16_t
|
||||
|
||||
let depth_data_length = (width * height) as isize;
|
||||
|
||||
let mut points = Vec::new();
|
||||
|
||||
for i in 0 .. depth_data_length {
|
||||
unsafe {
|
||||
// TODO: This is missing `isnan` checks.
|
||||
// if (depth_data[i] != 0 && !isnan(xy_table_data[i].xy.x) && !isnan(xy_table_data[i].xy.y))
|
||||
if (*depth_data.offset(i)) != 0 {
|
||||
let x = (*xy_table_data.offset(i)).xy.x * ((*depth_data.offset(i)) as f32);
|
||||
let y = (*xy_table_data.offset(i)).xy.y * ((*depth_data.offset(i)) as f32);
|
||||
let z = (*depth_data.offset(i)) as f32;
|
||||
points.push(Point::at(x, y, z, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(points)
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct ColorCameraPoint {
|
||||
pub b: u8,
|
||||
pub g: u8,
|
||||
pub r: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
// Directly return as a vector.
|
||||
pub fn calculate_point_cloud3(depth_image: &Image, xy_table_image: &Image, color_image: &Image) -> AnyhowResult<Vec<Point>> {
|
||||
let width = depth_image.get_width_pixels();
|
||||
let height = depth_image.get_height_pixels();
|
||||
|
||||
{
|
||||
let xy_width = xy_table_image.get_width_pixels();
|
||||
let xy_height = xy_table_image.get_height_pixels();
|
||||
if width != xy_width || height != xy_height {
|
||||
return Err(anyhow!("Depth image ({}x{}) and XY table ({}x{}) dimensions are not equal!",
|
||||
width, height, xy_width, xy_height));
|
||||
}
|
||||
}
|
||||
|
||||
let xy_table_data = xy_table_image.get_buffer() as *mut k4a_sys::k4a_float2_t;
|
||||
let depth_data = depth_image.get_buffer() as *mut u16; // uint16_t
|
||||
let color_data = color_image.get_buffer() as *mut ColorCameraPoint;
|
||||
|
||||
let depth_data_length = (width * height) as isize;
|
||||
|
||||
let mut points = Vec::new();
|
||||
|
||||
for i in 0 .. depth_data_length {
|
||||
unsafe {
|
||||
// TODO: This is missing `isnan` checks.
|
||||
// if (depth_data[i] != 0 && !isnan(xy_table_data[i].xy.x) && !isnan(xy_table_data[i].xy.y))
|
||||
if (*depth_data.offset(i)) != 0 {
|
||||
let x = (*xy_table_data.offset(i)).xy.x * ((*depth_data.offset(i)) as f32);
|
||||
let y = (*xy_table_data.offset(i)).xy.y * ((*depth_data.offset(i)) as f32);
|
||||
let z = (*depth_data.offset(i)) as f32;
|
||||
let color = (*color_data.offset(i)) as ColorCameraPoint;
|
||||
|
||||
let point_color = Color::Custom {
|
||||
r: color.r,
|
||||
b: color.b,
|
||||
g: color.g,
|
||||
a: 255
|
||||
};
|
||||
|
||||
points.push(Point::at(x, y, z, point_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(points)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use crate::AnyhowResult;
|
||||
use anyhow::anyhow;
|
||||
use k4a_sys_temp;
|
||||
use kinect::{Transformation, Image, Calibration, ImageFormat};
|
||||
use std::mem::size_of;
|
||||
|
||||
/// Transform Depth images into Color Camera space.
|
||||
pub struct DepthTransformer {
|
||||
transformation: Transformation,
|
||||
output_width: u32,
|
||||
output_height: u32,
|
||||
output_stride_bytes: u32,
|
||||
}
|
||||
|
||||
impl DepthTransformer {
|
||||
|
||||
pub fn new(calibration: &Calibration) -> Self {
|
||||
let transformation = Transformation::from_calibration(&calibration);
|
||||
|
||||
// TODO(bt): Temporary hardcoding for K4A_COLOR_RESOLUTION_2160P
|
||||
let output_width = transformation.color_resolution.width as u32;
|
||||
let output_height = transformation.color_resolution.height as u32;
|
||||
//let output_width = 3840; //transformation.color_resolution.width as u32;
|
||||
//let output_height = 2160; //transformation.color_resolution.height as u32;
|
||||
let output_stride_bytes = output_width * size_of::<libc::uint16_t>() as u32; // NB: Depth pixel
|
||||
|
||||
Self {
|
||||
transformation,
|
||||
output_width,
|
||||
output_height,
|
||||
output_stride_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform(&self, depth_image: &Image) -> AnyhowResult<Image> {
|
||||
let transform_h = self.transformation.get_handle();
|
||||
let depth_image_h = depth_image.get_handle();
|
||||
|
||||
println!("Width, height, stride: {}, {}, {}", self.output_width, self.output_height, self.output_stride_bytes);
|
||||
|
||||
let output_image = Image::create(
|
||||
ImageFormat::Depth16,
|
||||
self.output_width,
|
||||
self.output_height,
|
||||
self.output_stride_bytes,
|
||||
).map_err(|e| anyhow!("Can't create the image: {:?}", e))?;
|
||||
|
||||
println!("Image created");
|
||||
|
||||
let output_image_h = output_image.get_handle();
|
||||
println!("About to transform...");
|
||||
|
||||
let result = unsafe {
|
||||
k4a_sys_temp::k4a_transformation_depth_image_to_color_camera(
|
||||
transform_h,
|
||||
depth_image_h,
|
||||
output_image_h,
|
||||
)
|
||||
};
|
||||
println!("Transformed");
|
||||
|
||||
if result != k4a_sys_temp::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
|
||||
return Err(anyhow!("Depth transformation failed."));
|
||||
}
|
||||
|
||||
Ok(output_image)
|
||||
}
|
||||
|
||||
// TODO
|
||||
//pub fn transform_in_place(&mut self) {
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use cgmath::num_traits::Float;
|
||||
use crate::zeromq::point::Point;
|
||||
|
||||
/// Print the minima and maxima along each dimension.
|
||||
pub fn print_pointcloud_maxima(points: &Vec<Point>) {
|
||||
let mut max_x = f32::min_value();
|
||||
let mut min_x = f32::max_value();
|
||||
let mut max_y = f32::min_value();
|
||||
let mut min_y = f32::max_value();
|
||||
let mut max_z = f32::min_value();
|
||||
let mut min_z = f32::max_value();
|
||||
|
||||
for pt in points.iter() {
|
||||
if pt.x < min_x {
|
||||
min_x = pt.x;
|
||||
}
|
||||
if pt.x > max_x {
|
||||
max_x = pt.x;
|
||||
}
|
||||
if pt.y < min_y {
|
||||
min_y = pt.y;
|
||||
}
|
||||
if pt.y > max_y {
|
||||
max_y = pt.y;
|
||||
}
|
||||
if pt.z < min_z {
|
||||
min_z = pt.z;
|
||||
}
|
||||
if pt.z > max_z {
|
||||
max_z = pt.z;
|
||||
}
|
||||
}
|
||||
|
||||
// minmax x : -2612.5303, 4902.1655 | y: -3491.938, 1000.92487 | z: 163, 5494
|
||||
// minmax x : -2615.739, 4918.527 | y: -3507.2483, 1007.1275 | z: 163, 5518
|
||||
println!("minmax x : {}, {} | y: {}, {} | z: {}, {}",
|
||||
min_x, max_x, min_y, max_y, min_z, max_z);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use crate::zeromq::point::Point;
|
||||
use byteorder::{WriteBytesExt, LittleEndian};
|
||||
use std::io::Write;
|
||||
|
||||
/*
|
||||
|
||||
|
||||
/// Denotes the command that sends the variable-length data
|
||||
const POINT_DATA_BEGIN_PAYLOAD_COMMAND : u32 = 2;
|
||||
|
||||
/// Denotes the command that sends the variable-length data
|
||||
const POINT_DATA_CONTINUE_PAYLOAD_COMMAND : u32 = 3;
|
||||
|
||||
/// The maximum number of points to send per ZeroMQ "packet".
|
||||
/// This is also defined in C++, so it needs to be adjusted in multiple places.
|
||||
const MAX_SEND_POINTS_PER_PACKET : usize = 3000;
|
||||
|
||||
/// Streaming points
|
||||
struct StreamingPointsPacket {
|
||||
is_first_point_packet: bool,
|
||||
points: Vec<Point>,
|
||||
}
|
||||
|
||||
impl StreamingPointsPacket {
|
||||
fn from_all_points(points: Vec<Point>) -> Self {
|
||||
Self {
|
||||
is_first_point_packet: true,
|
||||
points,
|
||||
}
|
||||
}
|
||||
|
||||
/// Over the wire, this looks like:
|
||||
///
|
||||
/// | command | 4 bytes (u32) |
|
||||
/// | point_count | 4 bytes (u32) |
|
||||
/// | points[] ... | [16 bytes] ... |
|
||||
///
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
let point_bytes = Point::size_bytes() * MAX_SEND_POINTS_PER_PACKET;
|
||||
|
||||
let mut buf = Vec::with_capacity(4 + point_bytes);
|
||||
|
||||
if self.is_first_point_packet {
|
||||
buf.write_u32::<LittleEndian>(POINT_DATA_BEGIN_PAYLOAD_COMMAND); // COMMAND #
|
||||
} else {
|
||||
buf.write_u32::<LittleEndian>(POINT_DATA_CONTINUE_PAYLOAD_COMMAND); // COMMAND #
|
||||
}
|
||||
|
||||
//let subset = if points.len() > MAX_SEND_POINTS_PER_PACKET {
|
||||
// points.drain(0..MAX_SEND_POINTS_PER_PACKET).collect::<Vec<Point>>()
|
||||
//} else {
|
||||
// points.drain(0..points.len()).collect::<Vec<Point>>()
|
||||
//};
|
||||
|
||||
buf.write_u32::<LittleEndian>(self.points.len() as u32); // LENGTH
|
||||
|
||||
for point in self.points.iter() {
|
||||
let bytes = point.to_bytes();
|
||||
buf.write_all(&bytes);
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod cpu_calculate_point_cloud;
|
||||
pub mod color;
|
||||
pub mod debug;
|
||||
pub mod message;
|
||||
pub mod point;
|
||||
pub mod cpu_transformation;
|
||||
pub mod xy_table;
|
||||
@@ -0,0 +1,78 @@
|
||||
use byteorder::{WriteBytesExt, LittleEndian, BigEndian};
|
||||
use bytes::{BytesMut, BufMut};
|
||||
use crate::zeromq::color::Color;
|
||||
use rand::Rng;
|
||||
use rand::distributions::Uniform;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Point {
|
||||
// Position
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
// Color
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
|
||||
pub fn size_bytes() -> usize {
|
||||
16 // 3xf32, 4xu8
|
||||
}
|
||||
|
||||
pub fn at(x: f32, y: f32, z: f32, color: Color) -> Self {
|
||||
Point {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
r: color.get_u8_r(),
|
||||
g: color.get_u8_g(),
|
||||
b: color.get_u8_b(),
|
||||
a: color.get_u8_a(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at_random_range(min: f32, max: f32, color: Color) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let range = Uniform::new_inclusive(min, max);
|
||||
|
||||
let x = rng.sample(&range);
|
||||
let y = rng.sample(&range);
|
||||
let z = rng.sample(&range);
|
||||
|
||||
Point {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
r: color.get_u8_r(),
|
||||
g: color.get_u8_g(),
|
||||
b: color.get_u8_b(),
|
||||
a: color.get_u8_a(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(32);
|
||||
buf.write_f32::<LittleEndian>(self.x);
|
||||
buf.write_f32::<LittleEndian>(self.y);
|
||||
buf.write_f32::<LittleEndian>(self.z);
|
||||
buf.write_u8(self.r);
|
||||
buf.write_u8(self.g);
|
||||
buf.write_u8(self.b);
|
||||
buf.write_u8(self.a);
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn location_string(&self) -> String {
|
||||
format!("{}, {}, {}", self.x, self.y, self.z)
|
||||
}
|
||||
|
||||
pub fn debug_string(&self) -> String {
|
||||
format!("(location: {}, {}, {}); (color: {}, {}, {}, alpha: {})",
|
||||
self.x, self.y, self.z,
|
||||
self.r, self.g, self.b, self.a)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use crate::AnyhowResult;
|
||||
use k4a_sys_temp as k4a_sys;
|
||||
use kinect::{Calibration, Image, ImageFormat};
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
/**
|
||||
* Creates an XY depth lookup table. (Taken from MIT-licensed `fastpointcloud.exe` in the K4A libs; doc below)
|
||||
*
|
||||
* The Azure Kinect Fastpointcloud example computes a 3d point cloud from a depth map. The example precomputes a lookup table
|
||||
* by storing x- and y-scale factors for every pixel. At runtime, the 3d X-coordinate of a pixel in millimeters is derived
|
||||
* by multiplying the pixel's depth value with the corresponding x-scale factor. The 3d Y-coordinate is obtained by
|
||||
* multiplying with the y-scale factor.
|
||||
*
|
||||
* This method represents an alternative to calling k4a_transformation_depth_image_to_point_cloud() and lends itself
|
||||
* to efficient implementation on the GPU.
|
||||
*/
|
||||
pub fn create_xy_table_from_depth_calibration(calibration: &Calibration) -> AnyhowResult<Image> {
|
||||
let width = calibration.depth_camera_resolution_width();
|
||||
let height = calibration.depth_camera_resolution_height();
|
||||
create_xy_table(calibration, width, height)
|
||||
}
|
||||
|
||||
pub fn create_xy_table_from_color_calibration(calibration: &Calibration) -> AnyhowResult<Image> {
|
||||
let width = calibration.color_camera_resolution_width();
|
||||
let height = calibration.color_camera_resolution_height();
|
||||
create_xy_table(calibration, width, height)
|
||||
}
|
||||
|
||||
pub fn create_xy_table(calibration: &Calibration, width: i32, height: i32) -> AnyhowResult<Image> {
|
||||
let image = Image::create(
|
||||
ImageFormat::Custom,
|
||||
width as u32,
|
||||
height as u32,
|
||||
width as u32 * size_of::<k4a_sys::k4a_float2_t>() as u32,
|
||||
)?;
|
||||
|
||||
let typed_buffer = image.get_buffer() as *mut k4a_sys::k4a_float2_t;
|
||||
|
||||
let mut p = k4a_sys::k4a_float2_t {
|
||||
xy: k4a_sys::k4a_float2_t__xy {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
}
|
||||
};
|
||||
|
||||
let mut ray = k4a_sys::k4a_float3_t {
|
||||
xyz: k4a_sys::k4a_float3_t__xyz {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
}
|
||||
};
|
||||
|
||||
let mut idx = 0;
|
||||
|
||||
for y in 0 .. height {
|
||||
p.xy.y = y as f32;
|
||||
|
||||
for x in 0 .. width {
|
||||
p.xy.x = x as f32;
|
||||
|
||||
let mut valid: c_int = -1;
|
||||
let result = unsafe {
|
||||
k4a_sys::k4a_calibration_2d_to_3d(
|
||||
&calibration.0,
|
||||
&p, // source point 2d
|
||||
1.0, // source depth mm
|
||||
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH, // source camera
|
||||
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH, // target camera
|
||||
&mut ray, // target point3d mm
|
||||
&mut valid // set to 1 when valid result, 0 when coordinate is not valid
|
||||
)
|
||||
};
|
||||
|
||||
if valid == 1 {
|
||||
unsafe {
|
||||
(*typed_buffer.offset(idx)).xy.x = ray.xyz.x;
|
||||
(*typed_buffer.offset(idx)).xy.y = ray.xyz.y;
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
(*typed_buffer.offset(idx)).xy.x = 0.0;
|
||||
(*typed_buffer.offset(idx)).xy.y = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
let mut reconnect = false;
|
||||
let mut fail_count = 0;
|
||||
|
||||
/*
|
||||
let color = get_color();
|
||||
let point = Point::at_random_range(-1000.0f32, 1000.0f32, color);
|
||||
let bytes = point.to_bytes();
|
||||
//println!("Point : {}", point.debug_string());
|
||||
|
||||
if reconnect {
|
||||
socket = reconnect_socket(&context, socket, SOCKET_ADDRESS);
|
||||
reconnect = false;
|
||||
messaging_state = MessagingState::Sending;
|
||||
}
|
||||
|
||||
match messaging_state {
|
||||
MessagingState::Sending => {
|
||||
//println!("Sending request...");
|
||||
//let result = socket.send("hello world!", 0);
|
||||
let result = socket.send(&bytes, 0);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
//println!("Sent!");
|
||||
//thread::sleep(Duration::from_millis(250));
|
||||
messaging_state = MessagingState::Receiving;
|
||||
},
|
||||
Err(e) => {
|
||||
//eprintln!("Send Error ({}): {:?}", e.to_raw(), e);
|
||||
//thread::sleep(Duration::from_millis(250));
|
||||
fail_count += 1;
|
||||
},
|
||||
}
|
||||
|
||||
},
|
||||
MessagingState::Receiving => {
|
||||
//println!("Awaiting response...");
|
||||
let result = socket.recv_bytes(DONTWAIT);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
//println!("Response received!");
|
||||
messaging_state = MessagingState::Sending;
|
||||
},
|
||||
Err(e) => {
|
||||
//eprintln!("Recv Error ({}): {:?}", e.to_raw(), e);
|
||||
//thread::sleep(Duration::from_millis(250));
|
||||
fail_count += 1;
|
||||
},
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
if fail_count > 5 {
|
||||
reconnect = true;
|
||||
fail_count = 0;
|
||||
//thread::sleep(Duration::from_millis(2000));
|
||||
}
|
||||
*/
|
||||
Reference in new issue
Block a user