From 8ec43425bb9d00e37d9e2b8ba640d36e4eaafe42 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 15:51:37 +0100 Subject: [PATCH 01/17] implement support for raw 3D meshes (aka triangle soups) --- crates/re_analytics/Cargo.toml | 2 +- .../src/component_types/mesh3d.rs | 169 +++++++++++------- .../re_log_types/src/component_types/size.rs | 1 + .../re_log_types/src/component_types/vec.rs | 6 + crates/re_log_types/src/path/entity_path.rs | 10 ++ crates/re_renderer/src/importer/gltf.rs | 5 + crates/re_renderer/src/mesh.rs | 2 +- crates/re_viewer/src/misc/mesh_loader.rs | 78 +++++--- .../src/ui/data_ui/component_ui_registry.rs | 16 ++ crates/rerun_sdk/src/lib.rs | 41 +++++ 10 files changed, 242 insertions(+), 88 deletions(-) diff --git a/crates/re_analytics/Cargo.toml b/crates/re_analytics/Cargo.toml index 6b3da0cd13f6..47dabdd28c73 100644 --- a/crates/re_analytics/Cargo.toml +++ b/crates/re_analytics/Cargo.toml @@ -25,7 +25,7 @@ derive_more.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] directories-next = "2" -reqwest = { version = "0.11", default-features = false, features = [ +reqwest = { workspace = true, default-features = false, features = [ "blocking", "rustls-tls", ] } diff --git a/crates/re_log_types/src/component_types/mesh3d.rs b/crates/re_log_types/src/component_types/mesh3d.rs index 5a69b40ca053..7efefb53da1c 100644 --- a/crates/re_log_types/src/component_types/mesh3d.rs +++ b/crates/re_log_types/src/component_types/mesh3d.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use arrow2::array::{FixedSizeBinaryArray, MutableFixedSizeBinaryArray}; use arrow2::datatypes::DataType; use arrow2_convert::arrow_enable_vec_for_type; @@ -72,25 +74,80 @@ impl ArrowDeserialize for MeshId { // ---------------------------------------------------------------------------- -// TODO(#749) Re-enable `RawMesh3D` -// These seem totally unused at the moment and not even supported by the SDK -#[derive(Clone, Debug, PartialEq)] +// TODO(cmc): Let's make both mesh Component types use friendlier types for their inner elements +// (e.g. positions should be a vec of Vec3D, transform should be a Mat4, etc). +// This will also make error checking for invalid user data much nicer. +// +// But first let's do the python example and see how everything starts to take shape... + +// TODO(cmc): Let's move all the RefCounting stuff to the top-level. + +/// ``` +/// # use re_log_types::component_types::RawMesh3D; +/// # use arrow2_convert::field::ArrowField; +/// # use arrow2::datatypes::{DataType, Field, UnionMode}; +/// assert_eq!( +/// RawMesh3D::data_type(), +/// DataType::Struct(vec![ +/// Field::new("mesh_id", DataType::FixedSizeBinary(16), false), +/// Field::new("positions", DataType::List(Box::new( +/// Field::new("item", DataType::Float32, false)), +/// ), false), +/// Field::new("indices", DataType::List(Box::new( +/// Field::new("item", DataType::UInt32, false)), +/// ), true), +/// Field::new("normals", DataType::List(Box::new( +/// Field::new("item", DataType::Float32, false)), +/// ), true), +/// ]), +/// ); +/// ``` +// TODO(cmc): This currently doesn't specify nor checks in any way that it expects triangle lists. +#[derive(ArrowField, ArrowSerialize, ArrowDeserialize, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct RawMesh3D { pub mesh_id: MeshId, - pub positions: Vec<[f32; 3]>, - pub indices: Vec<[u32; 3]>, + pub positions: Vec, + pub indices: Option>, + pub normals: Option>, + // TODO(cmc): We need to support vertex colors and/or texturing, otherwise it's pretty + // hard to see anything with complex enough meshes (and hovering doesn't really help + // when everything's white). + // pub colors: Option>, + // pub texcoords: Option>, } // ---------------------------------------------------------------------------- /// Compressed/encoded mesh format +/// +/// ``` +/// # use re_log_types::component_types::EncodedMesh3D; +/// # use arrow2_convert::field::ArrowField; +/// # use arrow2::datatypes::{DataType, Field, UnionMode}; +/// assert_eq!( +/// EncodedMesh3D::data_type(), +/// DataType::Struct(vec![ +/// Field::new("mesh_id", DataType::FixedSizeBinary(16), false), +/// Field::new("format", DataType::Union(vec![ +/// Field::new("Gltf", DataType::Boolean, false), +/// Field::new("Glb", DataType::Boolean, false), +/// Field::new("Obj", DataType::Boolean, false), +/// ], None, UnionMode::Dense), false), +/// Field::new("bytes", DataType::Binary, false), +/// Field::new("transform", DataType::FixedSizeList( +/// Box::new(Field::new("item", DataType::Float32, false)), +/// 12, +/// ), false), +/// ]), +/// ); +/// ``` #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct EncodedMesh3D { pub mesh_id: MeshId, pub format: MeshFormat, - pub bytes: std::sync::Arc<[u8]>, + pub bytes: Arc<[u8]>, /// four columns of an affine transformation matrix pub transform: [[f32; 3]; 4], } @@ -202,47 +259,20 @@ impl std::fmt::Display for MeshFormat { } } -/// A Generic 3D Mesh +/// A Generic 3D Mesh. +/// +/// Cheaply clonable as it is all refcounted internally. /// /// ``` -/// # use re_log_types::component_types::Mesh3D; +/// # use re_log_types::component_types::{Mesh3D, EncodedMesh3D, RawMesh3D}; /// # use arrow2_convert::field::ArrowField; /// # use arrow2::datatypes::{DataType, Field, UnionMode}; /// assert_eq!( /// Mesh3D::data_type(), -/// DataType::Union( -/// vec![Field::new( -/// "Encoded", -/// DataType::Struct(vec![ -/// Field::new("mesh_id", DataType::FixedSizeBinary(16), false), -/// Field::new( -/// "format", -/// DataType::Union( -/// vec![ -/// Field::new("Gltf", DataType::Boolean, false), -/// Field::new("Glb", DataType::Boolean, false), -/// Field::new("Obj", DataType::Boolean, false) -/// ], -/// None, -/// UnionMode::Dense -/// ), -/// false -/// ), -/// Field::new("bytes", DataType::Binary, false), -/// Field::new( -/// "transform", -/// DataType::FixedSizeList( -/// Box::new(Field::new("item", DataType::Float32, false)), -/// 12 -/// ), -/// false -/// ) -/// ]), -/// false -/// )], -/// None, -/// UnionMode::Dense -/// ) +/// DataType::Union(vec![ +/// Field::new("Encoded", EncodedMesh3D::data_type(), false), +/// Field::new("Raw", RawMesh3D::data_type(), false), +/// ], None, UnionMode::Dense), /// ); /// ``` #[derive(Clone, Debug, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)] @@ -250,8 +280,7 @@ impl std::fmt::Display for MeshFormat { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum Mesh3D { Encoded(EncodedMesh3D), - // TODO(#749) Re-enable `RawMesh3D` - // Raw(Arc), + Raw(RawMesh3D), } impl Component for Mesh3D { @@ -264,32 +293,50 @@ impl Mesh3D { pub fn mesh_id(&self) -> MeshId { match self { Mesh3D::Encoded(mesh) => mesh.mesh_id, - // TODO(#749) Re-enable `RawMesh3D` - // Mesh3D::Raw(mesh) => mesh.mesh_id, + Mesh3D::Raw(mesh) => mesh.mesh_id, } } } -#[test] -fn test_datatype() {} +// #[test] +// fn test_mesh_je() { +// dbg!(Mesh3D::data_type()); +// assert!(false) +// } #[test] fn test_mesh_roundtrip() { use arrow2::array::Array; use arrow2_convert::{deserialize::TryIntoCollection, serialize::TryIntoArrow}; - let mesh_in = vec![Mesh3D::Encoded(EncodedMesh3D { - mesh_id: MeshId::random(), - format: MeshFormat::Glb, - bytes: std::sync::Arc::new([5, 9, 13, 95, 38, 42, 98, 17]), - transform: [ - [1.0, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0], - [10.0, 11.0, 12.], - ], - })]; - let array: Box = mesh_in.try_into_arrow().unwrap(); - let mesh_out: Vec = TryIntoCollection::try_into_collection(array).unwrap(); - assert_eq!(mesh_in, mesh_out); + // Encoded + { + let mesh_in = vec![Mesh3D::Encoded(EncodedMesh3D { + mesh_id: MeshId::random(), + format: MeshFormat::Glb, + bytes: std::sync::Arc::new([5, 9, 13, 95, 38, 42, 98, 17]), + transform: [ + [1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0], + [10.0, 11.0, 12.], + ], + })]; + let array: Box = mesh_in.try_into_arrow().unwrap(); + let mesh_out: Vec = TryIntoCollection::try_into_collection(array).unwrap(); + assert_eq!(dbg!(mesh_in), dbg!(mesh_out)); + } + + // Raw + { + let mesh_in = vec![Mesh3D::Raw(RawMesh3D { + mesh_id: MeshId::random(), + positions: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 9.0, 10.0], + indices: vec![1, 2, 3].into(), + normals: vec![10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 80.0, 90.0, 100.0].into(), + })]; + let array: Box = mesh_in.try_into_arrow().unwrap(); + let mesh_out: Vec = TryIntoCollection::try_into_collection(array).unwrap(); + assert_eq!(mesh_in, mesh_out); + } } diff --git a/crates/re_log_types/src/component_types/size.rs b/crates/re_log_types/src/component_types/size.rs index fb4ba63d84e9..640401955138 100644 --- a/crates/re_log_types/src/component_types/size.rs +++ b/crates/re_log_types/src/component_types/size.rs @@ -2,6 +2,7 @@ use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; use crate::msg_bundle::Component; +// TODO(cmc): should just embed a Vec3D? #[derive(Debug, ArrowField, ArrowSerialize, ArrowDeserialize)] pub struct Size3D { pub x: f32, diff --git a/crates/re_log_types/src/component_types/vec.rs b/crates/re_log_types/src/component_types/vec.rs index e819b8eea765..c9baa1d6aa6d 100644 --- a/crates/re_log_types/src/component_types/vec.rs +++ b/crates/re_log_types/src/component_types/vec.rs @@ -3,6 +3,8 @@ use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; use super::FixedSizeArrayField; use crate::msg_bundle::Component; +// --- Vec2D --- + /// A vector in 2D space. /// /// ``` @@ -71,6 +73,8 @@ impl From for Vec2D { } } +// --- Vec3D --- + /// A vector in 3D space. /// /// ``` @@ -145,6 +149,8 @@ impl From for Vec3D { } } +// --- Vec4D --- + /// A vector in 4D space. /// /// ``` diff --git a/crates/re_log_types/src/path/entity_path.rs b/crates/re_log_types/src/path/entity_path.rs index a6abc3f01225..259c7332118b 100644 --- a/crates/re_log_types/src/path/entity_path.rs +++ b/crates/re_log_types/src/path/entity_path.rs @@ -126,6 +126,16 @@ impl EntityPath { pub fn parent(&self) -> Option { self.path.parent().map(Self::from) } + + pub fn join(&self, other: &Self) -> Self { + self.iter().chain(other.iter()).cloned().collect() + } +} + +impl FromIterator for EntityPath { + fn from_iter>(parts: T) -> Self { + Self::new(parts.into_iter().collect()) + } } impl From for EntityPath { diff --git a/crates/re_renderer/src/importer/gltf.rs b/crates/re_renderer/src/importer/gltf.rs index 5f1255e7de94..fa6d62fc4d64 100644 --- a/crates/re_renderer/src/importer/gltf.rs +++ b/crates/re_renderer/src/importer/gltf.rs @@ -16,6 +16,11 @@ use crate::{ }; /// Loads both gltf and glb into the mesh & texture manager. +// +// TODO(cmc): We need to load the the default skinning transforms if they exist, and traverse +// the scene differently in that case. +// It's apparently very common for glTF meshes in the wild to define default skinning transforms +// for their initial orientation. pub fn load_gltf_from_buffer( mesh_name: &str, buffer: &[u8], diff --git a/crates/re_renderer/src/mesh.rs b/crates/re_renderer/src/mesh.rs index 2f26a41f22e7..bc08f6cc6548 100644 --- a/crates/re_renderer/src/mesh.rs +++ b/crates/re_renderer/src/mesh.rs @@ -23,7 +23,7 @@ pub mod mesh_vertices { /// Mesh vertex as used in gpu residing vertex buffers. #[repr(C, packed)] - #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] + #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] pub struct MeshVertexData { pub normal: glam::Vec3, // TODO(andreas): Compress. Afaik Octahedral Mapping is the best by far, see https://jcgt.org/published/0003/02/01/ pub texcoord: glam::Vec2, diff --git a/crates/re_viewer/src/misc/mesh_loader.rs b/crates/re_viewer/src/misc/mesh_loader.rs index 528d3e8fb4f8..b0f5ef97c29b 100644 --- a/crates/re_viewer/src/misc/mesh_loader.rs +++ b/crates/re_viewer/src/misc/mesh_loader.rs @@ -19,13 +19,12 @@ impl LoadedMesh { ) -> anyhow::Result { // TODO(emilk): load CpuMesh in background thread. match mesh { - // Mesh from user logging some triangles. + // Mesh from some file format. File passed in bytes. Mesh3D::Encoded(encoded_mesh) => { Self::load_encoded_mesh(name, encoded_mesh, render_ctx) } - // Mesh from some file format. File passed in bytes. - // TODO(#749) Re-enable `RawMesh3D` - //Mesh3D::Raw(raw_mesh) => Ok(Self::load_raw_mesh(name, raw_mesh, render_ctx)?), + // Mesh from user logging some triangles. + Mesh3D::Raw(raw_mesh) => Ok(Self::load_raw_mesh(name, raw_mesh, render_ctx)?), } } @@ -71,6 +70,7 @@ impl LoadedMesh { let mut slf = Self::load_raw(name, *format, bytes, render_ctx)?; + // TODO(cmc): Why are we creating the matrix twice here? let (scale, rotation, translation) = glam::Affine3A::from_cols_array_2d(transform).to_scale_rotation_translation(); let transform = @@ -83,8 +83,6 @@ impl LoadedMesh { Ok(slf) } - // TODO(#749) Re-enable `RawMesh3D` - #[allow(dead_code)] fn load_raw_mesh( name: String, raw_mesh: &RawMesh3D, @@ -92,9 +90,51 @@ impl LoadedMesh { ) -> anyhow::Result { crate::profile_function!(); - let bbox = macaw::BoundingBox::from_points( - raw_mesh.positions.iter().map(|p| glam::Vec3::from(*p)), - ); + // TODO(cmc): Having to do all of these data conversions, copies and allocations doesn't + // really make sense when you consider that both the component and the renderer are native + // Rust. Need to clean all of that up later. + + let RawMesh3D { + mesh_id: _, + positions, + indices, + normals, + } = raw_mesh; + + let positions = positions + .chunks_exact(3) + .map(|v| glam::Vec3::from([v[0], v[1], v[2]])) + .collect::>(); + let nb_positions = positions.len(); + + let indices = if let Some(indices) = indices { + indices.clone() + } else { + (0..positions.len() as u32).collect() + }; + let nb_indices = indices.len(); + + let normals = if let Some(normals) = normals { + normals + .chunks_exact(3) + .map(|v| glam::Vec3::from([v[0], v[1], v[2]])) + .map(|normal| re_renderer::mesh::mesh_vertices::MeshVertexData { + normal, + texcoord: glam::Vec2::ZERO, + }) + .collect::>() + } else { + // TODO(andreas): Calculate normals + // TODO(cmc): support colored and/or textured raw meshes + std::iter::repeat(re_renderer::mesh::mesh_vertices::MeshVertexData { + normal: glam::Vec3::ZERO, + texcoord: glam::Vec2::ZERO, + }) + .take(nb_positions) + .collect() + }; + + let bbox = macaw::BoundingBox::from_points(positions.iter().copied()); let mesh_instances = vec![re_renderer::renderer::MeshInstance { gpu_mesh: render_ctx.mesh_manager.create( @@ -102,24 +142,12 @@ impl LoadedMesh { &render_ctx.texture_manager_2d, &re_renderer::mesh::Mesh { label: name.clone().into(), - indices: raw_mesh.indices.iter().flatten().cloned().collect(), - vertex_positions: raw_mesh - .positions - .iter() - .map(|p| glam::Vec3::from(*p)) - .collect(), - // TODO(andreas): Calculate normals - vertex_data: std::iter::repeat( - re_renderer::mesh::mesh_vertices::MeshVertexData { - normal: glam::Vec3::ZERO, - texcoord: glam::Vec2::ZERO, - }, - ) - .take(raw_mesh.positions.len()) - .collect(), + indices, + vertex_positions: positions, + vertex_data: normals, materials: smallvec::smallvec![re_renderer::mesh::Material { label: name.clone().into(), - index_range: 0..raw_mesh.indices.len() as _, + index_range: 0..nb_indices as _, albedo: render_ctx.texture_manager_2d.white_texture_handle().clone(), albedo_multiplier: re_renderer::Rgba::WHITE, }], diff --git a/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs b/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs index 746390ad6d44..81f40001d947 100644 --- a/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs +++ b/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs @@ -184,6 +184,7 @@ impl DataUi for re_log_types::component_types::Mesh3D { ) { match self { re_log_types::Mesh3D::Encoded(mesh) => mesh.data_ui(ctx, ui, verbosity, query), + re_log_types::Mesh3D::Raw(mesh) => mesh.data_ui(ctx, ui, verbosity, query), } } } @@ -199,3 +200,18 @@ impl DataUi for re_log_types::component_types::EncodedMesh3D { ui.label(format!("{} mesh", self.format)); } } + +impl DataUi for re_log_types::component_types::RawMesh3D { + fn data_ui( + &self, + _ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + _verbosity: UiVerbosity, + _query: &re_arrow_store::LatestAtQuery, + ) { + ui.label(format!( + "mesh ({} triangles)", + re_format::format_number(self.positions.len() / 3) + )); + } +} diff --git a/crates/rerun_sdk/src/lib.rs b/crates/rerun_sdk/src/lib.rs index bfd71056bc20..8ea5e65e79d5 100644 --- a/crates/rerun_sdk/src/lib.rs +++ b/crates/rerun_sdk/src/lib.rs @@ -16,3 +16,44 @@ mod global; pub use self::global::global_session; pub mod viewer; + +// TODO(cmc): clean all that up? + +pub use re_log_types::msg_bundle::MsgBundle; +pub use re_log_types::{EntityPath, LogMsg, MsgId}; +pub use re_log_types::{Time, TimePoint, TimeType, Timeline}; + +// TODO(cmc): separate datatypes (e.g. Vec3D) from components (e.g. Size3D). +pub use re_log_types::component_types::AnnotationContext; +pub use re_log_types::component_types::Arrow3D; +pub use re_log_types::component_types::Box3D; +pub use re_log_types::component_types::ClassId; +pub use re_log_types::component_types::ColorRGBA; +pub use re_log_types::component_types::Instance; +pub use re_log_types::component_types::KeypointId; +pub use re_log_types::component_types::Label; +pub use re_log_types::component_types::Mat3x3; +pub use re_log_types::component_types::Quaternion; +pub use re_log_types::component_types::Radius; +pub use re_log_types::component_types::Rect2D; +pub use re_log_types::component_types::Size3D; +pub use re_log_types::component_types::TextEntry; +pub use re_log_types::component_types::{ + coordinates::{Axis3, Handedness, Sign, SignedAxis3}, + ViewCoordinates, +}; +pub use re_log_types::component_types::{EncodedMesh3D, Mesh3D, MeshFormat, MeshId, RawMesh3D}; +pub use re_log_types::component_types::{LineStrip2D, LineStrip3D}; +pub use re_log_types::component_types::{Pinhole, Rigid3, Transform}; +pub use re_log_types::component_types::{Point2D, Point3D}; +pub use re_log_types::component_types::{Scalar, ScalarPlotProps}; +pub use re_log_types::component_types::{ + Tensor, TensorData, TensorDataMeaning, TensorDimension, TensorId, TensorTrait, +}; +pub use re_log_types::component_types::{Vec2D, Vec3D, Vec4D}; + +pub mod reexports { + pub use re_log; + pub use re_log_types; + pub use re_memory; +} From 99790a1872006e251ad2456fbe6a4839275d613a Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 15:52:07 +0100 Subject: [PATCH 02/17] added raw_mesh rust example --- examples/raw_mesh/Cargo.toml | 17 ++ examples/raw_mesh/src/main.rs | 322 ++++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 examples/raw_mesh/Cargo.toml create mode 100644 examples/raw_mesh/src/main.rs diff --git a/examples/raw_mesh/Cargo.toml b/examples/raw_mesh/Cargo.toml new file mode 100644 index 000000000000..17e26569df0c --- /dev/null +++ b/examples/raw_mesh/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "raw_mesh" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +bytes = "1.3" +gltf.workspace = true +mimalloc = "0.1" +reqwest = { workspace = true, default-features = false, features = [ + "blocking", + "rustls-tls", +] } + +# TODO(cmc): is the crate actually gonna be called just 'rerun' in the end? +rerun = { package = "rerun_sdk", path = "../../crates/rerun_sdk" } diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs new file mode 100644 index 000000000000..a0cb07e0f9f0 --- /dev/null +++ b/examples/raw_mesh/src/main.rs @@ -0,0 +1,322 @@ +#![allow(clippy::doc_markdown)] + +use std::path::PathBuf; + +use anyhow::Context; +use rerun::{ + reexports::{re_log, re_memory::AccountingAllocator}, + EntityPath, LogMsg, Mesh3D, MeshId, MsgBundle, MsgId, RawMesh3D, Session, Time, TimePoint, + TimeType, Timeline, Transform, ViewCoordinates, +}; + +// TODO(cmc): This example needs to support animations to showcase Rerun's time capabilities. + +// --- Rerun logging --- + +// Declare how to turn a glTF primitive into a Rerun component (`Mesh3D`). +impl From for Mesh3D { + fn from(primitive: GltfPrimitive) -> Self { + Mesh3D::Raw(RawMesh3D { + mesh_id: MeshId::random(), + indices: primitive.indices, + positions: primitive.positions.into_iter().flatten().collect(), + normals: primitive + .normals + .map(|normals| normals.into_iter().flatten().collect()), + // TODO(cmc): We need to support vertex colors and/or texturing, otherwise it's pretty + // hard to see anything with complex enough meshes (and hovering doesn't really help + // when everything's white). + // colors: primitive + // .colors + // .map(|colors| colors.into_iter().flatten().collect()), + // texcoords: primitive + // .texcoords + // .map(|texcoords| texcoords.into_iter().flatten().collect()), + }) + } +} + +// Declare how to turn a glTF transform into a Rerun component (`Transform`). +impl From for Transform { + fn from(transform: GltfTransform) -> Self { + Transform::Rigid3(rerun::Rigid3 { + rotation: rerun::Quaternion { + x: transform.r[0], + y: transform.r[1], + z: transform.r[2], + w: transform.r[3], + }, + translation: rerun::Vec3D(transform.t), + }) + } +} + +/// Log a glTF node with Rerun. +fn log_node(session: &mut Session, node: GltfNode) { + let ent_path = EntityPath::from(node.name.as_str()); + + // What time is it? + let timeline_keyframe = Timeline::new("keyframe", TimeType::Sequence); + let time_point = TimePoint::from([ + // TODO(cmc): this _has_ to be inserted by the SDK + (Timeline::log_time(), Time::now().into()), + // TODO(cmc): animations! + (timeline_keyframe, 0.into()), + ]); + + // Convert glTF objects into Rerun components. + let transform = node.transform.map(Transform::from); + let primitives = node + .primitives + .into_iter() + .map(Mesh3D::from) + .collect::>(); + + // TODO(cmc): Transforms have to be logged separately because they are neither batches nor + // splats... the user shouldn't have to know that though! + // The SDK needs to split things up as needed when it sees a Transform component. + // + // We're going to have the same issue with splats: the SDK needs to automagically detect the + // user intention to use splats and do the necessary (like the python SDK does iirc). + if let Some(transform) = transform { + let bundle = MsgBundle::new( + MsgId::random(), + ent_path.clone(), + time_point.clone(), + // TODO(cmc): need to reproduce the viewer crash I had earlier and fix/log-an-issue for + // it. + vec![vec![transform].try_into().unwrap()], + ); + // TODO(cmc): These last conversion details need to be hidden in the SDK. + let msg = bundle.try_into().unwrap(); + session.send(LogMsg::ArrowMsg(msg)); + } + + // TODO(cmc): Working at the `ComponentBundle`/`TryIntoArrow` layer feels too low-level, + // something like a MsgBuilder kinda thing would probably be quite nice. + let bundle = MsgBundle::new( + MsgId::random(), + ent_path, + time_point, + vec![primitives.try_into().unwrap()], + ); + + // Create and send one message to the sdk + // TODO(cmc): These last conversion details need to be hidden in the SDK. + let msg = bundle.try_into().unwrap(); + session.send(LogMsg::ArrowMsg(msg)); + + // Recurse through all of the node's children! + for mut child in node.children { + child.name = [node.name.as_str(), child.name.as_str()].join("/"); + log_node(session, child); + } +} + +// TODO(cmc): The SDK should make this call so trivial that it doesn't require this helper at all. +fn log_axis(session: &mut Session, ent_path: &EntityPath) { + // From the glTF spec: + // > glTF uses a right-handed coordinate system. glTF defines +Y as up, +Z as forward, and + // > -X as right; the front of a glTF asset faces +Z. + let view_coords: ViewCoordinates = "RUB".parse().unwrap(); + + let bundle = MsgBundle::new( + MsgId::random(), + ent_path.clone(), + [].into(), // TODO: SDK + vec![vec![view_coords].try_into().unwrap()], + ); + + let msg = bundle.try_into().unwrap(); + session.send(LogMsg::ArrowMsg(msg)); +} + +// --- Init --- + +// TODO(cmc): This should probably just be a compile flag of the SDK; if it's enabled, then the +// global allocator is properly set by the SDK directly. +#[global_allocator] +static GLOBAL: AccountingAllocator = + AccountingAllocator::new(mimalloc::MiMalloc); + +fn main() -> anyhow::Result<()> { + re_log::setup_native_logging(); + + // TODO(cmc): Here we shall pass argv to the SDK which will strip it out of all SDK flags, and + // give us back our actual CLI flags. + // The name of the gltf sample to load should then come from there. + + // Load glTF asset + let bytes = download_gltf_sample("Buggy").unwrap(); + + // Parse glTF asset + let (doc, buffers, _) = gltf::import_slice(bytes).unwrap(); + let nodes = load_gltf(&doc, &buffers); + + // Log raw glTF nodes and their transforms with Rerun + let mut session = Session::new(); + for root in nodes { + re_log::info!(scene = root.name, "logging glTF scene"); + log_axis(&mut session, &root.name.as_str().into()); + log_node(&mut session, root); + } + + // TODO(cmc): provide high-level tools to pick and handle the different modes. + // TODO(cmc): connect, spawn_and_connect; show() probably doesn't make sense with pure rust + let log_messages = session.drain_log_messages_buffer(); + rerun::viewer::show(log_messages).context("failed to start viewer") +} + +// --- glTF parsing --- + +struct GltfNode { + name: String, + transform: Option, + primitives: Vec, + children: Vec, +} + +struct GltfPrimitive { + positions: Vec<[f32; 3]>, + indices: Option>, + normals: Option>, + #[allow(dead_code)] + colors: Option>, + #[allow(dead_code)] + texcoords: Option>, +} + +struct GltfTransform { + t: [f32; 3], + r: [f32; 4], + #[allow(dead_code)] + s: [f32; 3], +} + +impl GltfNode { + fn from_gltf(buffers: &[gltf::buffer::Data], node: &gltf::Node<'_>) -> Self { + let name = node_name(node); + + let transform = { + let (t, r, s) = node.transform().decomposed(); + GltfTransform { t, r, s } + }; + let primitives = node_primitives(buffers, node).collect(); + + let children = node + .children() + .map(|child| GltfNode::from_gltf(buffers, &child)) + .collect(); + + Self { + name, + transform: Some(transform), + primitives, + children, + } + } +} + +fn node_name(node: &gltf::Node<'_>) -> String { + node.name() + // TODO(cmc): now that index-paths and instance-keys are decorrelated, maybe we can use + // those here. + .map_or_else(|| format!("node_#{}", node.index()), ToOwned::to_owned) +} + +fn node_primitives<'data>( + buffers: &'data [gltf::buffer::Data], + node: &'data gltf::Node<'_>, +) -> impl Iterator + 'data { + node.mesh().into_iter().flat_map(|mesh| { + mesh.primitives().map(|primitive| { + assert!(primitive.mode() == gltf::mesh::Mode::Triangles); + + let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); + + let positions = reader.read_positions().unwrap(); + let positions = positions.collect(); + + let indices = reader.read_indices(); + let indices = indices.map(|indices| indices.into_u32().into_iter().collect()); + + let normals = reader.read_normals(); + let normals = normals.map(|normals| normals.collect()); + + let colors = reader.read_colors(0); // TODO(cmc): pick correct set + let colors = colors.map(|colors| colors.into_rgba_u8().collect()); + + let texcoords = reader.read_tex_coords(0); // TODO(cmc): pick correct set + let texcoords = texcoords.map(|texcoords| texcoords.into_f32().collect()); + + GltfPrimitive { + positions, + indices, + normals, + colors, + texcoords, + } + }) + }) +} + +// TODO(cmc): This is unfortunately _not_ the right way to load a glTF scene. +// We need to load the the default skinning transforms if they exist, and traverse the scene +// differently in that case. +// It's apparently very common for glTF meshes in the wild to define default skinning transforms +// for their initial orientation. +fn load_gltf<'data>( + doc: &'data gltf::Document, + buffers: &'data [gltf::buffer::Data], +) -> impl Iterator + 'data { + doc.scenes().map(move |scene| { + let name = scene + .name() + // TODO(cmc): now that index-paths and instance-keys are decorrelated, maybe we can use + // those here. + .map_or_else(|| format!("scene_#{}", scene.index()), ToOwned::to_owned); + + re_log::info!(scene = name, "parsing glTF scene"); + + GltfNode { + name, + transform: None, + primitives: Default::default(), + children: scene + .nodes() + .map(|node| GltfNode::from_gltf(buffers, &node)) + .collect(), + } + }) +} + +// --- Assets --- + +// TODO(cmc): This needs to be implemented and exposed by the SDK (probably behind a feature flag), +// and can probably be re-used by the Python examples too. +fn download_gltf_sample(name: impl AsRef) -> anyhow::Result { + const GLTF_SAMPLE_URL: &str = "https://github.com/KhronosGroup/glTF-Sample-Models/blob/db9ff67c1116cfe28eb36320916bccd8c4127cc1/2.0/_NAME_/glTF-Binary/_NAME_.glb?raw=true"; + + let url = GLTF_SAMPLE_URL.replace("_NAME_", name.as_ref()); + + let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("dataset/samples"); + let path = dir.join(format!("{}.glb", name.as_ref().to_lowercase())); + + std::fs::create_dir_all(&dir) + .with_context(|| format!("failed to create asset directory: {dir:?}"))?; + + if let Ok(bytes) = std::fs::read(&path) { + re_log::info!(asset = ?path, "loading asset from disk cache..."); + Ok(bytes::Bytes::from(bytes)) + } else { + re_log::info!(asset = ?path, "loading asset from network..."); + let res = reqwest::blocking::get(&url) + .with_context(|| format!("failed to fetch asset: {url}"))?; + let bytes = res + .bytes() + .with_context(|| format!("failed to fetch asset: {url}"))?; + std::fs::write(&path, &bytes) + .with_context(|| format!("failed to write asset to disk: {path:?}"))?; + Ok(bytes) + } +} From d404456fe8089717062bf303a0b5273d72f4a1bb Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 15:57:37 +0100 Subject: [PATCH 03/17] forgot to add those --- Cargo.lock | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2e2ac222526..f39b2d557062 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2285,6 +2285,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -2896,6 +2909,24 @@ dependencies = [ "getrandom", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndarray" version = "0.15.6" @@ -3242,6 +3273,51 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "3.4.0" @@ -3734,6 +3810,18 @@ dependencies = [ "cty", ] +[[package]] +name = "raw_mesh" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "gltf", + "mimalloc", + "reqwest", + "rerun_sdk", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -4281,10 +4369,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4294,6 +4384,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -4532,6 +4623,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4578,6 +4678,29 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.14" @@ -5192,6 +5315,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -5486,6 +5619,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec1" version = "1.10.1" diff --git a/Cargo.toml b/Cargo.toml index 5d6047980956..e21607143408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["crates/*", "rerun_py", "run_wasm"] +members = ["crates/*", "rerun_py", "run_wasm", "examples/raw_mesh"] [workspace.package] version = "0.1.0" @@ -31,12 +31,12 @@ polars-core = "0.26" polars-lazy = "0.26" polars-ops = "0.26" puffin = "0.14" +reqwest = "0.11" thiserror = "1.0" tokio = "1.24" wgpu = { version = "0.15", default-features = false } wgpu-core = { version = "0.15", default-features = false } - # Because gltf hasn't published a new version: https://github.com/gltf-rs/gltf/issues/357 gltf = { git = "https://github.com/rerun-io/gltf", rev = "3c14ded73755d1ce9e47010edb06db63cb7e2cca" } From 937c58215e0a12358a6abf54cee1a3ab2606c622 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 15:59:13 +0100 Subject: [PATCH 04/17] missed one --- examples/raw_mesh/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index a0cb07e0f9f0..6d28dd92a3a5 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -123,7 +123,7 @@ fn log_axis(session: &mut Session, ent_path: &EntityPath) { let bundle = MsgBundle::new( MsgId::random(), ent_path.clone(), - [].into(), // TODO: SDK + [].into(), // TODO(cmc): doing timeless stuff shouldn't be so weird vec![vec![view_coords].try_into().unwrap()], ); From b3e3e65b30f308e85b94a74eec924fc65838e9f7 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 16:02:20 +0100 Subject: [PATCH 05/17] Instance -> InstanceKey --- crates/rerun_sdk/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rerun_sdk/src/lib.rs b/crates/rerun_sdk/src/lib.rs index 8ea5e65e79d5..b589c64b03ea 100644 --- a/crates/rerun_sdk/src/lib.rs +++ b/crates/rerun_sdk/src/lib.rs @@ -29,7 +29,7 @@ pub use re_log_types::component_types::Arrow3D; pub use re_log_types::component_types::Box3D; pub use re_log_types::component_types::ClassId; pub use re_log_types::component_types::ColorRGBA; -pub use re_log_types::component_types::Instance; +pub use re_log_types::component_types::InstanceKey; pub use re_log_types::component_types::KeypointId; pub use re_log_types::component_types::Label; pub use re_log_types::component_types::Mat3x3; From 2eb98c9b4af798cfa57faed3fb77a397f2820311 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 16:03:39 +0100 Subject: [PATCH 06/17] inherit pkg attrs --- examples/raw_mesh/Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/raw_mesh/Cargo.toml b/examples/raw_mesh/Cargo.toml index 17e26569df0c..e66050c6de2c 100644 --- a/examples/raw_mesh/Cargo.toml +++ b/examples/raw_mesh/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "raw_mesh" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +publish = false [dependencies] anyhow.workspace = true From f18ba94d5079851e700d3ac2bb17522ce9af59d6 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 16:17:02 +0100 Subject: [PATCH 07/17] crate level doc --- examples/raw_mesh/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index 6d28dd92a3a5..1d0b02c2fb87 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -1,3 +1,7 @@ +//! This example demonstrates how to use the Rerun Rust SDK to log raw 3D meshes (so-called +//! "triangle soups") and their transform hierarchy. +//! + #![allow(clippy::doc_markdown)] use std::path::PathBuf; From ffd877c326ebfdab8c5f29a647eb38fe2d673c29 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 16:19:21 +0100 Subject: [PATCH 08/17] misbehaving features --- Cargo.lock | 127 --------------------------------- Cargo.toml | 2 +- crates/re_analytics/Cargo.toml | 2 +- examples/raw_mesh/Cargo.toml | 2 +- examples/raw_mesh/src/main.rs | 4 ++ 5 files changed, 7 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f39b2d557062..b0f14eaa07c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2285,19 +2285,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -2909,24 +2896,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndarray" version = "0.15.6" @@ -3273,51 +3242,6 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -[[package]] -name = "openssl" -version = "0.10.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "ordered-float" version = "3.4.0" @@ -4369,12 +4293,10 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4384,7 +4306,6 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -4623,15 +4544,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -4678,29 +4590,6 @@ dependencies = [ "tiny-skia", ] -[[package]] -name = "security-framework" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.14" @@ -5315,16 +5204,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.23.4" @@ -5619,12 +5498,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vec1" version = "1.10.1" diff --git a/Cargo.toml b/Cargo.toml index e21607143408..a7b1627dba8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ polars-core = "0.26" polars-lazy = "0.26" polars-ops = "0.26" puffin = "0.14" -reqwest = "0.11" +reqwest = { version = "0.11", default-features = false } thiserror = "1.0" tokio = "1.24" wgpu = { version = "0.15", default-features = false } diff --git a/crates/re_analytics/Cargo.toml b/crates/re_analytics/Cargo.toml index 47dabdd28c73..c891cf669697 100644 --- a/crates/re_analytics/Cargo.toml +++ b/crates/re_analytics/Cargo.toml @@ -25,7 +25,7 @@ derive_more.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] directories-next = "2" -reqwest = { workspace = true, default-features = false, features = [ +reqwest = { workspace = true, features = [ "blocking", "rustls-tls", ] } diff --git a/examples/raw_mesh/Cargo.toml b/examples/raw_mesh/Cargo.toml index e66050c6de2c..a8adea82cfa5 100644 --- a/examples/raw_mesh/Cargo.toml +++ b/examples/raw_mesh/Cargo.toml @@ -11,7 +11,7 @@ anyhow.workspace = true bytes = "1.3" gltf.workspace = true mimalloc = "0.1" -reqwest = { workspace = true, default-features = false, features = [ +reqwest = { workspace = true, features = [ "blocking", "rustls-tls", ] } diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index 1d0b02c2fb87..dca5affa4772 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -1,6 +1,10 @@ //! This example demonstrates how to use the Rerun Rust SDK to log raw 3D meshes (so-called //! "triangle soups") and their transform hierarchy. //! +//! Usage: +//! ``` +//! cargo run -p raw_mesh +//! ``` #![allow(clippy::doc_markdown)] From 44970f17b23c3fbea7caf5eeec249661cf8b2f5e Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 16:19:41 +0100 Subject: [PATCH 09/17] self review --- crates/re_log_types/src/component_types/mesh3d.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/re_log_types/src/component_types/mesh3d.rs b/crates/re_log_types/src/component_types/mesh3d.rs index 7efefb53da1c..8af2f49d3845 100644 --- a/crates/re_log_types/src/component_types/mesh3d.rs +++ b/crates/re_log_types/src/component_types/mesh3d.rs @@ -298,12 +298,6 @@ impl Mesh3D { } } -// #[test] -// fn test_mesh_je() { -// dbg!(Mesh3D::data_type()); -// assert!(false) -// } - #[test] fn test_mesh_roundtrip() { use arrow2::array::Array; From a48df23624d77a0299eceb13dddac1b1791e57ec Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 31 Jan 2023 16:22:57 +0100 Subject: [PATCH 10/17] just format --- crates/re_analytics/Cargo.toml | 5 +---- examples/raw_mesh/Cargo.toml | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/re_analytics/Cargo.toml b/crates/re_analytics/Cargo.toml index c891cf669697..ff66ab5341b3 100644 --- a/crates/re_analytics/Cargo.toml +++ b/crates/re_analytics/Cargo.toml @@ -25,10 +25,7 @@ derive_more.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] directories-next = "2" -reqwest = { workspace = true, features = [ - "blocking", - "rustls-tls", -] } +reqwest = { workspace = true, features = ["blocking", "rustls-tls"] } [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3.58", features = ["Storage"] } diff --git a/examples/raw_mesh/Cargo.toml b/examples/raw_mesh/Cargo.toml index a8adea82cfa5..0701dbede0be 100644 --- a/examples/raw_mesh/Cargo.toml +++ b/examples/raw_mesh/Cargo.toml @@ -11,10 +11,7 @@ anyhow.workspace = true bytes = "1.3" gltf.workspace = true mimalloc = "0.1" -reqwest = { workspace = true, features = [ - "blocking", - "rustls-tls", -] } +reqwest = { workspace = true, features = ["blocking", "rustls-tls"] } # TODO(cmc): is the crate actually gonna be called just 'rerun' in the end? rerun = { package = "rerun_sdk", path = "../../crates/rerun_sdk" } From a9f649ffda48e5259e457db2510b89615709642b Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 1 Feb 2023 08:36:59 +0100 Subject: [PATCH 11/17] keep the global allocator stuff non magical --- examples/raw_mesh/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index dca5affa4772..e6b4c72b78ab 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -141,8 +141,8 @@ fn log_axis(session: &mut Session, ent_path: &EntityPath) { // --- Init --- -// TODO(cmc): This should probably just be a compile flag of the SDK; if it's enabled, then the -// global allocator is properly set by the SDK directly. +// Use MiMalloc as global allocator, wrapped in Rerun's allocation tracker for easily monitor +// what's going on. #[global_allocator] static GLOBAL: AccountingAllocator = AccountingAllocator::new(mimalloc::MiMalloc); From 0e37877938e2dda9f1b5e1533b9eb056a28c1342 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 1 Feb 2023 14:39:58 +0100 Subject: [PATCH 12/17] Update examples/raw_mesh/src/main.rs Co-authored-by: Emil Ernerfeldt --- examples/raw_mesh/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index e6b4c72b78ab..2c74ff19ff72 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -141,8 +141,8 @@ fn log_axis(session: &mut Session, ent_path: &EntityPath) { // --- Init --- -// Use MiMalloc as global allocator, wrapped in Rerun's allocation tracker for easily monitor -// what's going on. +// Use MiMalloc as global allocator (because it is fast), wrapped in Rerun's allocation tracker +// so that the rerun viewer can show how much memory it is using when calling `show`. #[global_allocator] static GLOBAL: AccountingAllocator = AccountingAllocator::new(mimalloc::MiMalloc); From 6fffbc0c4ea087961ffd00d4c17a9b9da91ba8fe Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 1 Feb 2023 15:34:34 +0100 Subject: [PATCH 13/17] addressed PR comments --- .../src/component_types/mesh3d.rs | 35 ++++++++++++++++++- crates/re_viewer/src/misc/mesh_loader.rs | 7 ++-- .../src/ui/data_ui/component_ui_registry.rs | 2 +- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/crates/re_log_types/src/component_types/mesh3d.rs b/crates/re_log_types/src/component_types/mesh3d.rs index 8af2f49d3845..157fcf0f26b6 100644 --- a/crates/re_log_types/src/component_types/mesh3d.rs +++ b/crates/re_log_types/src/component_types/mesh3d.rs @@ -82,6 +82,8 @@ impl ArrowDeserialize for MeshId { // TODO(cmc): Let's move all the RefCounting stuff to the top-level. +/// A raw "triangle soup" mesh. +/// /// ``` /// # use re_log_types::component_types::RawMesh3D; /// # use arrow2_convert::field::ArrowField; @@ -102,13 +104,23 @@ impl ArrowDeserialize for MeshId { /// ]), /// ); /// ``` -// TODO(cmc): This currently doesn't specify nor checks in any way that it expects triangle lists. #[derive(ArrowField, ArrowSerialize, ArrowDeserialize, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct RawMesh3D { pub mesh_id: MeshId, + /// The flattened positions array of this mesh. + /// + /// Meshes are always triangle lists, i.e. the length of this vector should always be + /// divisible by 3. pub positions: Vec, + /// Optionally, the flattened indices array for this mesh. + /// + /// Meshes are always triangle lists, i.e. the length of this vector should always be + /// divisible by 3. pub indices: Option>, + /// Optionally, the flattened normals array for this mesh. + /// + /// If specified, this must match the length of `Self::positions`. pub normals: Option>, // TODO(cmc): We need to support vertex colors and/or texturing, otherwise it's pretty // hard to see anything with complex enough meshes (and hovering doesn't really help @@ -117,6 +129,27 @@ pub struct RawMesh3D { // pub texcoords: Option>, } +impl RawMesh3D { + pub fn sanity_check(&self) { + assert!(self.positions.len() % 3 == 0); + if let Some(indices) = &self.indices { + assert!(indices.len() % 3 == 0); + } + if let Some(normals) = &self.normals { + assert!(normals.len() == self.positions.len()); + } + } + + pub fn num_triangles(&self) -> usize { + // TODO(cmc): will need to properly expose actual mesh-related errors all the way down to + // the SDKs soon, but right now this will do. + #[cfg(debug_assertions)] + self.sanity_check(); + + self.positions.len() / 3 + } +} + // ---------------------------------------------------------------------------- /// Compressed/encoded mesh format diff --git a/crates/re_viewer/src/misc/mesh_loader.rs b/crates/re_viewer/src/misc/mesh_loader.rs index b0f5ef97c29b..0b51629764cf 100644 --- a/crates/re_viewer/src/misc/mesh_loader.rs +++ b/crates/re_viewer/src/misc/mesh_loader.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use re_log_types::{EncodedMesh3D, Mesh3D, MeshFormat, RawMesh3D}; use re_renderer::{resource_managers::ResourceLifeTime, RenderContext}; @@ -101,10 +102,8 @@ impl LoadedMesh { normals, } = raw_mesh; - let positions = positions - .chunks_exact(3) - .map(|v| glam::Vec3::from([v[0], v[1], v[2]])) - .collect::>(); + let positions: Vec = + bytemuck::try_cast_vec(positions.clone()).map_err(|(err, _)| anyhow!(err))?; let nb_positions = positions.len(); let indices = if let Some(indices) = indices { diff --git a/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs b/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs index 09eb56f0830b..9a9c32b58835 100644 --- a/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs +++ b/crates/re_viewer/src/ui/data_ui/component_ui_registry.rs @@ -211,7 +211,7 @@ impl DataUi for re_log_types::component_types::RawMesh3D { ) { ui.label(format!( "mesh ({} triangles)", - re_format::format_number(self.positions.len() / 3) + re_format::format_number(self.num_triangles()) )); } } From 80f5aede985bbc37663f9203669d7542ab410315 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 1 Feb 2023 15:43:31 +0100 Subject: [PATCH 14/17] rust examples won't handle assets download for now --- examples/raw_mesh/src/main.rs | 43 ++++++++--------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index 2c74ff19ff72..e91b8ad2d77f 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -10,7 +10,8 @@ use std::path::PathBuf; -use anyhow::Context; +use anyhow::{bail, Context}; +use bytes::Bytes; use rerun::{ reexports::{re_log, re_memory::AccountingAllocator}, EntityPath, LogMsg, Mesh3D, MeshId, MsgBundle, MsgId, RawMesh3D, Session, Time, TimePoint, @@ -154,8 +155,13 @@ fn main() -> anyhow::Result<()> { // give us back our actual CLI flags. // The name of the gltf sample to load should then come from there. - // Load glTF asset - let bytes = download_gltf_sample("Buggy").unwrap(); + // Read glTF asset + let args = std::env::args().collect::>(); + let bytes = if let Some(path) = args.get(1) { + Bytes::from(std::fs::read(path)?) + } else { + bail!("Usage: {} ", args[0]); + }; // Parse glTF asset let (doc, buffers, _) = gltf::import_slice(bytes).unwrap(); @@ -297,34 +303,3 @@ fn load_gltf<'data>( } }) } - -// --- Assets --- - -// TODO(cmc): This needs to be implemented and exposed by the SDK (probably behind a feature flag), -// and can probably be re-used by the Python examples too. -fn download_gltf_sample(name: impl AsRef) -> anyhow::Result { - const GLTF_SAMPLE_URL: &str = "https://github.com/KhronosGroup/glTF-Sample-Models/blob/db9ff67c1116cfe28eb36320916bccd8c4127cc1/2.0/_NAME_/glTF-Binary/_NAME_.glb?raw=true"; - - let url = GLTF_SAMPLE_URL.replace("_NAME_", name.as_ref()); - - let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("dataset/samples"); - let path = dir.join(format!("{}.glb", name.as_ref().to_lowercase())); - - std::fs::create_dir_all(&dir) - .with_context(|| format!("failed to create asset directory: {dir:?}"))?; - - if let Ok(bytes) = std::fs::read(&path) { - re_log::info!(asset = ?path, "loading asset from disk cache..."); - Ok(bytes::Bytes::from(bytes)) - } else { - re_log::info!(asset = ?path, "loading asset from network..."); - let res = reqwest::blocking::get(&url) - .with_context(|| format!("failed to fetch asset: {url}"))?; - let bytes = res - .bytes() - .with_context(|| format!("failed to fetch asset: {url}"))?; - std::fs::write(&path, &bytes) - .with_context(|| format!("failed to write asset to disk: {path:?}"))?; - Ok(bytes) - } -} From 5f017f4a6805c5b07c9bb66b8b941587d72ff050 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 1 Feb 2023 15:44:25 +0100 Subject: [PATCH 15/17] i must be wrong about this, surely?? --- crates/re_renderer/src/importer/gltf.rs | 5 ----- examples/raw_mesh/src/main.rs | 5 ----- 2 files changed, 10 deletions(-) diff --git a/crates/re_renderer/src/importer/gltf.rs b/crates/re_renderer/src/importer/gltf.rs index fa6d62fc4d64..5f1255e7de94 100644 --- a/crates/re_renderer/src/importer/gltf.rs +++ b/crates/re_renderer/src/importer/gltf.rs @@ -16,11 +16,6 @@ use crate::{ }; /// Loads both gltf and glb into the mesh & texture manager. -// -// TODO(cmc): We need to load the the default skinning transforms if they exist, and traverse -// the scene differently in that case. -// It's apparently very common for glTF meshes in the wild to define default skinning transforms -// for their initial orientation. pub fn load_gltf_from_buffer( mesh_name: &str, buffer: &[u8], diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index e91b8ad2d77f..f2eb968073c1 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -274,11 +274,6 @@ fn node_primitives<'data>( }) } -// TODO(cmc): This is unfortunately _not_ the right way to load a glTF scene. -// We need to load the the default skinning transforms if they exist, and traverse the scene -// differently in that case. -// It's apparently very common for glTF meshes in the wild to define default skinning transforms -// for their initial orientation. fn load_gltf<'data>( doc: &'data gltf::Document, buffers: &'data [gltf::buffer::Data], From 4da63b07bcdb882e211c7bd72c9cfd394a769e4e Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 1 Feb 2023 15:48:52 +0100 Subject: [PATCH 16/17] the paths are fine this way --- examples/raw_mesh/src/main.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/raw_mesh/src/main.rs b/examples/raw_mesh/src/main.rs index f2eb968073c1..89b98616f7ce 100644 --- a/examples/raw_mesh/src/main.rs +++ b/examples/raw_mesh/src/main.rs @@ -3,13 +3,11 @@ //! //! Usage: //! ``` -//! cargo run -p raw_mesh +//! cargo run -p raw_mesh //! ``` #![allow(clippy::doc_markdown)] -use std::path::PathBuf; - use anyhow::{bail, Context}; use bytes::Bytes; use rerun::{ @@ -160,7 +158,7 @@ fn main() -> anyhow::Result<()> { let bytes = if let Some(path) = args.get(1) { Bytes::from(std::fs::read(path)?) } else { - bail!("Usage: {} ", args[0]); + bail!("Usage: {} ", args[0]); }; // Parse glTF asset @@ -233,8 +231,6 @@ impl GltfNode { fn node_name(node: &gltf::Node<'_>) -> String { node.name() - // TODO(cmc): now that index-paths and instance-keys are decorrelated, maybe we can use - // those here. .map_or_else(|| format!("node_#{}", node.index()), ToOwned::to_owned) } @@ -281,8 +277,6 @@ fn load_gltf<'data>( doc.scenes().map(move |scene| { let name = scene .name() - // TODO(cmc): now that index-paths and instance-keys are decorrelated, maybe we can use - // those here. .map_or_else(|| format!("scene_#{}", scene.index()), ToOwned::to_owned); re_log::info!(scene = name, "parsing glTF scene"); From 12ae552de7ed14005e3fe187cfe662f81f9189cb Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Wed, 1 Feb 2023 15:49:42 +0100 Subject: [PATCH 17/17] lints --- crates/re_log_types/src/component_types/mesh3d.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/re_log_types/src/component_types/mesh3d.rs b/crates/re_log_types/src/component_types/mesh3d.rs index 157fcf0f26b6..1208567f23a4 100644 --- a/crates/re_log_types/src/component_types/mesh3d.rs +++ b/crates/re_log_types/src/component_types/mesh3d.rs @@ -351,7 +351,7 @@ fn test_mesh_roundtrip() { })]; let array: Box = mesh_in.try_into_arrow().unwrap(); let mesh_out: Vec = TryIntoCollection::try_into_collection(array).unwrap(); - assert_eq!(dbg!(mesh_in), dbg!(mesh_out)); + assert_eq!(mesh_in, mesh_out); } // Raw