#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use std::collections::HashMap;
use std::sync::Mutex;
use rerun::{RecordingStream, TimeColumn};
use schiebung::{BufferObserver, TransformType, TransformUpdate};
const DYNAMIC_ENTITY_PATH: &str = "tf";
const STATIC_ENTITY_PATH: &str = "tf_static";
pub struct RerunObserver {
rec: RecordingStream,
publish_static_transforms: bool,
timeline: String,
static_state: Mutex<HashMap<(String, String), Row>>,
}
impl RerunObserver {
pub fn new(rec: RecordingStream, publish_static_transforms: bool, timeline: String) -> Self {
RerunObserver {
rec,
publish_static_transforms,
timeline,
static_state: Mutex::new(HashMap::new()),
}
}
}
#[derive(Clone)]
struct Row {
parent: String,
child: String,
translation: [f32; 3],
quaternion: [f32; 4],
stamp_ns: i64,
}
fn row_from(update: &TransformUpdate) -> Row {
let t = update.stamped_isometry.translation();
let r = update.stamped_isometry.rotation();
Row {
parent: update.from.clone(),
child: update.to.clone(),
translation: [t[0] as f32, t[1] as f32, t[2] as f32],
quaternion: [r[0] as f32, r[1] as f32, r[2] as f32, r[3] as f32],
stamp_ns: update.stamped_isometry.stamp(),
}
}
impl BufferObserver for RerunObserver {
fn on_update(&self, updates: &[TransformUpdate]) {
let mut dynamic_rows: Vec<Row> = Vec::new();
let mut static_updates: Vec<Row> = Vec::new();
for update in updates {
match update.kind {
TransformType::Dynamic => dynamic_rows.push(row_from(update)),
TransformType::Static => {
if self.publish_static_transforms {
static_updates.push(row_from(update));
}
}
}
}
if !dynamic_rows.is_empty() {
self.send_dynamic(DYNAMIC_ENTITY_PATH, &dynamic_rows);
}
if !static_updates.is_empty() {
let snapshot: Vec<Row> = {
let mut state = self.static_state.lock().unwrap();
for row in static_updates {
state.insert((row.parent.clone(), row.child.clone()), row);
}
state.values().cloned().collect()
};
self.send_static(STATIC_ENTITY_PATH, &snapshot);
}
}
}
impl RerunObserver {
fn send_dynamic(&self, entity_path: &str, rows: &[Row]) {
let stamps: Vec<i64> = rows.iter().map(|r| r.stamp_ns).collect();
let time_column =
TimeColumn::new_timestamp_nanos_since_epoch(self.timeline.as_str(), stamps);
if let Some((tf_columns, frame_columns)) = build_columns(rows) {
self.rec
.send_columns(
entity_path.to_owned(),
[time_column],
tf_columns.chain(frame_columns),
)
.ok();
}
}
fn send_static(&self, entity_path: &str, rows: &[Row]) {
if let Some((tf_columns, frame_columns)) = build_columns(rows) {
self.rec
.send_columns(
entity_path.to_owned(),
std::iter::empty::<TimeColumn>(),
tf_columns.chain(frame_columns),
)
.ok();
}
}
}
#[allow(clippy::type_complexity)]
fn build_columns(
rows: &[Row],
) -> Option<(
impl Iterator<Item = rerun::SerializedComponentColumn>,
impl Iterator<Item = rerun::SerializedComponentColumn>,
)> {
let translations: Vec<[f32; 3]> = rows.iter().map(|r| r.translation).collect();
let quaternions: Vec<rerun::Quaternion> = rows
.iter()
.map(|r| rerun::Quaternion::from_xyzw(r.quaternion))
.collect();
let parents: Vec<String> = rows.iter().map(|r| r.parent.clone()).collect();
let children: Vec<String> = rows.iter().map(|r| r.child.clone()).collect();
let tf_columns = rerun::archetypes::Transform3D::update_fields()
.with_many_translation(translations)
.with_many_quaternion(quaternions)
.with_many_parent_frame(parents.clone())
.with_many_child_frame(children.clone())
.columns_of_unit_batches()
.ok()?;
let frame_columns = rerun::archetypes::CoordinateFrame::update_fields()
.with_many_frame(children)
.columns_of_unit_batches()
.ok()?;
Some((tf_columns, frame_columns))
}