Skip to content

Commit

Permalink
rust SDK: raw 3D meshes and example (#1010)
Browse files Browse the repository at this point in the history
* implement support for raw 3D meshes (aka triangle soups)

* added raw_mesh rust example

* forgot to add those

* missed one

* Instance -> InstanceKey

* inherit pkg attrs

* crate level doc

* misbehaving features

* self review

* just format

* keep the global allocator stuff non magical

* Update examples/raw_mesh/src/main.rs

Co-authored-by: Emil Ernerfeldt <emil@rerun.io>

* addressed PR comments

* rust examples won't handle assets download for now

* i must be wrong about this, surely??

* the paths are fine this way

* lints

---------

Co-authored-by: Emil Ernerfeldt <emil@rerun.io>
  • Loading branch information
teh-cmc and emilk committed Feb 1, 2023
1 parent 534384d commit 5d86a00
Show file tree
Hide file tree
Showing 13 changed files with 589 additions and 94 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -31,12 +31,12 @@ polars-core = "0.26"
polars-lazy = "0.26"
polars-ops = "0.26"
puffin = "0.14"
reqwest = { version = "0.11", default-features = false }
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" }

Expand Down
5 changes: 1 addition & 4 deletions crates/re_analytics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ derive_more.workspace = true

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
directories-next = "2"
reqwest = { version = "0.11", default-features = false, 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"] }
Expand Down
198 changes: 136 additions & 62 deletions crates/re_log_types/src/component_types/mesh3d.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -72,25 +74,113 @@ 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.

/// A raw "triangle soup" mesh.
///
/// ```
/// # 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),
/// ]),
/// );
/// ```
#[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]>,
/// 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<f32>,
/// 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<Vec<u32>>,
/// Optionally, the flattened normals array for this mesh.
///
/// If specified, this must match the length of `Self::positions`.
pub normals: Option<Vec<f32>>,
// 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<Vec<u8>>,
// pub texcoords: Option<Vec<f32>>,
}

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
///
/// ```
/// # 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],
}
Expand Down Expand Up @@ -202,56 +292,28 @@ 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)]
#[arrow_field(type = "dense")]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Mesh3D {
Encoded(EncodedMesh3D),
// TODO(#749) Re-enable `RawMesh3D`
// Raw(Arc<RawMesh3D>),
Raw(RawMesh3D),
}

impl Component for Mesh3D {
Expand All @@ -264,32 +326,44 @@ 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_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<dyn Array> = mesh_in.try_into_arrow().unwrap();
let mesh_out: Vec<Mesh3D> = 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<dyn Array> = mesh_in.try_into_arrow().unwrap();
let mesh_out: Vec<Mesh3D> = TryIntoCollection::try_into_collection(array).unwrap();
assert_eq!(mesh_in, 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<dyn Array> = mesh_in.try_into_arrow().unwrap();
let mesh_out: Vec<Mesh3D> = TryIntoCollection::try_into_collection(array).unwrap();
assert_eq!(mesh_in, mesh_out);
}
}
1 change: 1 addition & 0 deletions crates/re_log_types/src/component_types/size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions crates/re_log_types/src/component_types/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize};
use super::FixedSizeArrayField;
use crate::msg_bundle::Component;

// --- Vec2D ---

/// A vector in 2D space.
///
/// ```
Expand Down Expand Up @@ -71,6 +73,8 @@ impl From<glam::Vec2> for Vec2D {
}
}

// --- Vec3D ---

/// A vector in 3D space.
///
/// ```
Expand Down Expand Up @@ -145,6 +149,8 @@ impl From<glam::Vec3> for Vec3D {
}
}

// --- Vec4D ---

/// A vector in 4D space.
///
/// ```
Expand Down
10 changes: 10 additions & 0 deletions crates/re_log_types/src/path/entity_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ impl EntityPath {
pub fn parent(&self) -> Option<Self> {
self.path.parent().map(Self::from)
}

pub fn join(&self, other: &Self) -> Self {
self.iter().chain(other.iter()).cloned().collect()
}
}

impl FromIterator<EntityPathPart> for EntityPath {
fn from_iter<T: IntoIterator<Item = EntityPathPart>>(parts: T) -> Self {
Self::new(parts.into_iter().collect())
}
}

impl From<EntityPathImpl> for EntityPath {
Expand Down
2 changes: 1 addition & 1 deletion crates/re_renderer/src/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 5d86a00

Please sign in to comment.