diff --git a/clippy.toml b/clippy.toml index 6e08e8c4407c..09221aff8376 100644 --- a/clippy.toml +++ b/clippy.toml @@ -75,6 +75,7 @@ doc-valid-idents = [ "macOS", "NaN", "OBJ", + "OpenGL", "PyPI", "sRGB", "sRGBA", diff --git a/crates/re_data_ui/src/data.rs b/crates/re_data_ui/src/data.rs index 1e97e5bce722..2a74f9fc7d80 100644 --- a/crates/re_data_ui/src/data.rs +++ b/crates/re_data_ui/src/data.rs @@ -1,9 +1,7 @@ use egui::Vec2; use re_format::format_f32; -use re_types::components::{ - Color, LineStrip2D, LineStrip3D, Material, MeshProperties, ViewCoordinates, -}; +use re_types::components::{Color, LineStrip2D, LineStrip3D, MeshProperties, ViewCoordinates}; use re_viewer_context::{UiVerbosity, ViewerContext}; use super::{table_for_verbosity, DataUi}; @@ -227,38 +225,6 @@ impl DataUi for LineStrip3D { } } -impl DataUi for Material { - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - verbosity: UiVerbosity, - query: &re_data_store::LatestAtQuery, - store: &re_data_store::DataStore, - ) { - let show_optional_albedo_factor = |ui: &mut egui::Ui| { - if let Some(albedo_factor) = self.albedo_factor { - Color(albedo_factor).data_ui(ctx, ui, verbosity, query, store); - } else { - ui.weak("(empty)"); - } - }; - - match verbosity { - UiVerbosity::Small | UiVerbosity::Reduced => { - show_optional_albedo_factor(ui); - } - UiVerbosity::Full | UiVerbosity::LimitHeight => { - egui::Grid::new("material").num_columns(2).show(ui, |ui| { - ui.label("albedo_factor"); - show_optional_albedo_factor(ui); - ui.end_row(); - }); - } - } - } -} - impl DataUi for MeshProperties { fn data_ui( &self, diff --git a/crates/re_data_ui/src/image.rs b/crates/re_data_ui/src/image.rs index 4957ccc2edf2..571506da5106 100644 --- a/crates/re_data_ui/src/image.rs +++ b/crates/re_data_ui/src/image.rs @@ -65,7 +65,7 @@ impl EntityDataUi for re_types::components::TensorData { } #[allow(clippy::too_many_arguments)] -fn tensor_ui( +pub fn tensor_ui( ctx: &ViewerContext<'_>, query: &re_data_store::LatestAtQuery, store: &re_data_store::DataStore, diff --git a/crates/re_data_ui/src/lib.rs b/crates/re_data_ui/src/lib.rs index 77089b26490a..b8442438ca13 100644 --- a/crates/re_data_ui/src/lib.rs +++ b/crates/re_data_ui/src/lib.rs @@ -20,13 +20,15 @@ mod entity_path; mod image; mod image_meaning; mod instance_path; -pub mod item_ui; mod log_msg; +mod material; mod pinhole; mod rotation3d; mod store_id; mod transform3d; +pub mod item_ui; + pub use crate::image::{ show_zoomed_image_region, show_zoomed_image_region_area_outline, tensor_summary_ui_grid_contents, diff --git a/crates/re_data_ui/src/material.rs b/crates/re_data_ui/src/material.rs new file mode 100644 index 000000000000..d17aba5c17a9 --- /dev/null +++ b/crates/re_data_ui/src/material.rs @@ -0,0 +1,36 @@ +use re_types::components::{Color, Material}; +use re_viewer_context::{UiVerbosity, ViewerContext}; + +use crate::DataUi; + +impl DataUi for Material { + fn data_ui( + &self, + ctx: &ViewerContext<'_>, + ui: &mut egui::Ui, + verbosity: UiVerbosity, + query: &re_data_store::LatestAtQuery, + store: &re_data_store::DataStore, + ) { + let show_optional_albedo_factor = |ui: &mut egui::Ui| { + if let Some(albedo_factor) = self.albedo_factor { + Color(albedo_factor).data_ui(ctx, ui, verbosity, query, store); + } else { + ui.weak("(empty)"); + } + }; + + match verbosity { + UiVerbosity::Small | UiVerbosity::Reduced => { + show_optional_albedo_factor(ui); + } + UiVerbosity::Full | UiVerbosity::LimitHeight => { + egui::Grid::new("material").num_columns(2).show(ui, |ui| { + ui.label("albedo_factor"); + show_optional_albedo_factor(ui); + ui.end_row(); + }); + } + } + } +} diff --git a/crates/re_renderer/src/lib.rs b/crates/re_renderer/src/lib.rs index 8d6909a0e7b6..0206c696bc48 100644 --- a/crates/re_renderer/src/lib.rs +++ b/crates/re_renderer/src/lib.rs @@ -79,9 +79,13 @@ pub use self::file_resolver::{ }; pub use self::file_server::FileServer; -// Re-export used color types. +// Re-export used color types directly. pub use ecolor::{Color32, Hsva, Rgba}; +pub mod external { + pub use wgpu; +} + // --------------------------------------------------------------------------- // Make Arrow integration as transparent as possible. diff --git a/crates/re_space_view/src/data_query_blueprint.rs b/crates/re_space_view/src/data_query_blueprint.rs index ed3f00f96945..70b81a1caec0 100644 --- a/crates/re_space_view/src/data_query_blueprint.rs +++ b/crates/re_space_view/src/data_query_blueprint.rs @@ -419,7 +419,7 @@ impl DataQueryPropertyResolver<'_> { /// Recursively walk the [`DataResultTree`] and update the [`PropertyOverrides`] for each node. /// /// This will accumulate the group properties at each step down the tree, and then finally merge - /// with individual overrides at the leafs. + /// with individual overrides at the leaves. fn update_overrides_recursive( &self, ctx: &StoreContext<'_>, diff --git a/crates/re_space_view_spatial/src/mesh_cache.rs b/crates/re_space_view_spatial/src/mesh_cache.rs index f1de1d769556..bfc28fb4443b 100644 --- a/crates/re_space_view_spatial/src/mesh_cache.rs +++ b/crates/re_space_view_spatial/src/mesh_cache.rs @@ -9,7 +9,7 @@ use crate::mesh_loader::LoadedMesh; // ---------------------------------------------------------------------------- -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct MeshCacheKey { pub versioned_instance_path_hash: VersionedInstancePathHash, pub media_type: Option, @@ -24,7 +24,13 @@ pub struct MeshCache(ahash::HashMap>>); #[derive(Debug, Clone, Copy)] pub enum AnyMesh<'a> { Asset(&'a re_types::archetypes::Asset3D), - Mesh(&'a re_types::archetypes::Mesh3D), + Mesh { + mesh: &'a re_types::archetypes::Mesh3D, + + /// If there are any textures associated with that mesh (albedo etc), they use this + /// hash for texture manager lookup. + texture_key: u64, + }, } impl MeshCache { diff --git a/crates/re_space_view_spatial/src/mesh_loader.rs b/crates/re_space_view_spatial/src/mesh_loader.rs index b75e00ce929a..06c56c0bc42e 100644 --- a/crates/re_space_view_spatial/src/mesh_loader.rs +++ b/crates/re_space_view_spatial/src/mesh_loader.rs @@ -3,6 +3,7 @@ use re_renderer::{resource_managers::ResourceLifeTime, RenderContext, Rgba32Unmu use re_types::{ archetypes::{Asset3D, Mesh3D}, components::MediaType, + datatypes::TensorBuffer, }; use crate::mesh_cache::AnyMesh; @@ -26,7 +27,9 @@ impl LoadedMesh { // TODO(emilk): load CpuMesh in background thread. match mesh { AnyMesh::Asset(asset3d) => Self::load_asset3d(name, asset3d, render_ctx), - AnyMesh::Mesh(mesh3d) => Ok(Self::load_mesh3d(name, mesh3d, render_ctx)?), + AnyMesh::Mesh { mesh, texture_key } => { + Ok(Self::load_mesh3d(name, mesh, texture_key, render_ctx)?) + } } } @@ -85,6 +88,7 @@ impl LoadedMesh { fn load_mesh3d( name: String, mesh3d: &Mesh3D, + texture_key: u64, render_ctx: &RenderContext, ) -> anyhow::Result { re_tracing::profile_function!(); @@ -94,9 +98,11 @@ impl LoadedMesh { mesh_properties, vertex_normals, vertex_colors, + vertex_texcoords, mesh_material, class_ids: _, instance_keys: _, + albedo_texture, } = mesh3d; let vertex_positions: &[glam::Vec3] = bytemuck::cast_slice(vertex_positions.as_slice()); @@ -135,11 +141,16 @@ impl LoadedMesh { normals.iter().map(|v| v.0.into()).collect::>() } else { // TODO(andreas): Calculate normals - // TODO(cmc): support textured raw meshes vec![glam::Vec3::ZERO; num_positions] }; - let vertex_texcoords = vec![glam::Vec2::ZERO; vertex_normals.len()]; + let vertex_texcoords = if let Some(texcoords) = vertex_texcoords { + re_tracing::profile_scope!("collect_texcoords"); + texcoords.iter().map(|v| v.0.into()).collect::>() + } else { + vec![glam::Vec2::ZERO; num_positions] + }; + let albedo_factor = mesh_material.as_ref().and_then(|mat| mat.albedo_factor); let bbox = { @@ -147,6 +158,15 @@ impl LoadedMesh { macaw::BoundingBox::from_points(vertex_positions.iter().copied()) }; + let albedo = if let Some(albedo_texture) = &albedo_texture { + mesh_texture_from_tensor_data(&albedo_texture.0, render_ctx, texture_key)? + } else { + render_ctx + .texture_manager_2d + .white_texture_unorm_handle() + .clone() + }; + let mesh = re_renderer::mesh::Mesh { label: name.clone().into(), triangle_indices, @@ -157,10 +177,7 @@ impl LoadedMesh { materials: smallvec::smallvec![re_renderer::mesh::Material { label: name.clone().into(), index_range: 0..num_indices as _, - albedo: render_ctx - .texture_manager_2d - .white_texture_unorm_handle() - .clone(), + albedo, albedo_multiplier: albedo_factor.map_or(re_renderer::Rgba::WHITE, |c| c.into()), }], }; @@ -190,3 +207,34 @@ impl LoadedMesh { self.bbox } } + +fn mesh_texture_from_tensor_data( + albedo_texture: &re_types::datatypes::TensorData, + render_ctx: &RenderContext, + texture_key: u64, +) -> anyhow::Result { + let [height, width, depth] = + re_viewer_context::gpu_bridge::texture_height_width_channels(albedo_texture)?; + + re_viewer_context::gpu_bridge::try_get_or_create_texture(render_ctx, texture_key, || { + let data = match (depth, &albedo_texture.buffer) { + (3, TensorBuffer::U8(buf)) => re_renderer::pad_rgb_to_rgba(buf, u8::MAX).into(), + (4, TensorBuffer::U8(buf)) => bytemuck::cast_slice(buf.as_slice()).into(), + + _ => { + anyhow::bail!( + "Only 3 and 4 channel u8 tensor data is supported currently for mesh textures." + ); + } + }; + + Ok(re_renderer::resource_managers::Texture2DCreationDesc { + label: "mesh albedo texture from tensor data".into(), + data, + format: re_renderer::external::wgpu::TextureFormat::Rgba8UnormSrgb, + width, + height, + }) + }) + .map_err(|err| anyhow::format_err!("{err}")) +} diff --git a/crates/re_space_view_spatial/src/visualizers/meshes.rs b/crates/re_space_view_spatial/src/visualizers/meshes.rs index cccc3daae17b..d5009ac85dfa 100644 --- a/crates/re_space_view_spatial/src/visualizers/meshes.rs +++ b/crates/re_space_view_spatial/src/visualizers/meshes.rs @@ -3,7 +3,9 @@ use re_query::{ArchetypeView, QueryError}; use re_renderer::renderer::MeshInstance; use re_types::{ archetypes::Mesh3D, - components::{Color, InstanceKey, Material, MeshProperties, Position3D, Vector3D}, + components::{ + Color, InstanceKey, Material, MeshProperties, Position3D, TensorData, Texcoord2D, Vector3D, + }, }; use re_viewer_context::{ ApplicableEntities, IdentifiedViewSystem, SpaceViewSystemExecutionError, ViewContextCollection, @@ -81,8 +83,20 @@ impl Mesh3DVisualizer { } else { None }, + vertex_texcoords: if arch_view.has_component::() { + re_tracing::profile_scope!("vertex_texcoords"); + Some( + arch_view + .iter_optional_component::()? + .map(|comp| comp.unwrap_or(Texcoord2D::ZERO)) + .collect(), + ) + } else { + None + }, mesh_properties: arch_view.raw_optional_mono_component::()?, mesh_material: arch_view.raw_optional_mono_component::()?, + albedo_texture: arch_view.raw_optional_mono_component::()?, class_ids: None, instance_keys: None, } @@ -93,13 +107,17 @@ impl Mesh3DVisualizer { let outline_mask_ids = ent_context.highlight.index_outline_mask(InstanceKey::SPLAT); let mesh = ctx.cache.entry(|c: &mut MeshCache| { + let key = MeshCacheKey { + versioned_instance_path_hash: picking_instance_hash.versioned(primary_row_id), + media_type: None, + }; c.entry( &ent_path.to_string(), - MeshCacheKey { - versioned_instance_path_hash: picking_instance_hash.versioned(primary_row_id), - media_type: None, + key.clone(), + AnyMesh::Mesh { + mesh: &mesh, + texture_key: re_log_types::hash::Hash64::hash(&key).hash64(), }, - AnyMesh::Mesh(&mesh), ctx.render_ctx, ) }); diff --git a/crates/re_types/definitions/rerun/archetypes/mesh3d.fbs b/crates/re_types/definitions/rerun/archetypes/mesh3d.fbs index 6499c920f077..bc4c7afa5a18 100644 --- a/crates/re_types/definitions/rerun/archetypes/mesh3d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/mesh3d.fbs @@ -41,14 +41,24 @@ table Mesh3D ( /// An optional color for each vertex. vertex_colors: [rerun.components.Color] ("attr.rerun.component_optional", nullable, order: 3100); + /// An optional uv texture coordinate for each vertex. + vertex_texcoords: [rerun.components.Texcoord2D] ("attr.rerun.component_optional", nullable, order: 3200); + /// Optional material properties for the mesh as a whole. - mesh_material: rerun.components.Material ("attr.rerun.component_optional", nullable, order: 3200); + mesh_material: rerun.components.Material ("attr.rerun.component_optional", nullable, order: 3300); + + /// Optional albedo texture. + /// + /// Used with `vertex_texcoords` on `Mesh3D`. + /// Currently supports only sRGB(A) textures, ignoring alpha. + /// (meaning that the tensor must have 3 or 4 channels and use the `u8` format) + albedo_texture: rerun.components.TensorData ("attr.rerun.component_optional", nullable, order: 3400); /// Optional class Ids for the vertices. /// /// The class ID provides colors and labels if not specified explicitly. - class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3300); + class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3500); /// Unique identifiers for each individual vertex in the mesh. - instance_keys: [rerun.components.InstanceKey] ("attr.rerun.component_optional", nullable, order: 3400); + instance_keys: [rerun.components.InstanceKey] ("attr.rerun.component_optional", nullable, order: 3600); } diff --git a/crates/re_types/definitions/rerun/components.fbs b/crates/re_types/definitions/rerun/components.fbs index 3d6c9bd21bbb..7b113de9725a 100644 --- a/crates/re_types/definitions/rerun/components.fbs +++ b/crates/re_types/definitions/rerun/components.fbs @@ -25,6 +25,7 @@ include "./components/rotation3d.fbs"; include "./components/scalar_scattering.fbs"; include "./components/scalar.fbs"; include "./components/tensor_data.fbs"; +include "./components/texcoord2d.fbs"; include "./components/text_log_level.fbs"; include "./components/text.fbs"; include "./components/transform3d.fbs"; diff --git a/crates/re_types/definitions/rerun/components/texcoord2d.fbs b/crates/re_types/definitions/rerun/components/texcoord2d.fbs new file mode 100644 index 000000000000..8e2b0924f9fb --- /dev/null +++ b/crates/re_types/definitions/rerun/components/texcoord2d.fbs @@ -0,0 +1,37 @@ +include "arrow/attributes.fbs"; +include "python/attributes.fbs"; +include "rust/attributes.fbs"; + +include "rerun/attributes.fbs"; +include "rerun/datatypes.fbs"; + +namespace rerun.components; + +// --- + +/// A 2D texture UV coordinate. +/// +/// Texture coordinates specify a position on a 2D texture. +/// A range from 0-1 covers the entire texture in the respective dimension. +/// Unless configured otherwise, the texture repeats outside of this range. +/// Rerun uses top-left as the origin for UV coordinates. +/// +/// 0 U 1 +/// 0 + --------- → +/// | . +/// | . +/// V | . +/// | . +/// 1 ↓ . . . . . . +/// +/// This is the same convention as in Vulkan/Metal/DX12/WebGPU, but (!) unlike OpenGL, +/// which places the origin at the bottom-left. +struct Texcoord2D ( + "attr.python.aliases": "npt.NDArray[np.float32], Sequence[float], Tuple[float, float]", + "attr.python.array_aliases": "npt.NDArray[np.float32], Sequence[float]", + "attr.rust.derive": "Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent", + "attr.docs.unreleased" +) { + uv: rerun.datatypes.Vec2D (order: 100); +} diff --git a/crates/re_types/definitions/rerun/datatypes/tensor_data.fbs b/crates/re_types/definitions/rerun/datatypes/tensor_data.fbs index 5ff7be5a81a9..9c60455146e4 100644 --- a/crates/re_types/definitions/rerun/datatypes/tensor_data.fbs +++ b/crates/re_types/definitions/rerun/datatypes/tensor_data.fbs @@ -2,7 +2,7 @@ include "arrow/attributes.fbs"; include "fbs/attributes.fbs"; include "./tensor_dimension.fbs"; -include "./tensor_data.fbs"; +include "./tensor_buffer.fbs"; namespace rerun.datatypes; diff --git a/crates/re_types/src/archetypes/mesh3d.rs b/crates/re_types/src/archetypes/mesh3d.rs index ebb4ebc9e6ca..1c3fefb458af 100644 --- a/crates/re_types/src/archetypes/mesh3d.rs +++ b/crates/re_types/src/archetypes/mesh3d.rs @@ -71,9 +71,19 @@ pub struct Mesh3D { /// An optional color for each vertex. pub vertex_colors: Option>, + /// An optional uv texture coordinate for each vertex. + pub vertex_texcoords: Option>, + /// Optional material properties for the mesh as a whole. pub mesh_material: Option, + /// Optional albedo texture. + /// + /// Used with `vertex_texcoords` on `Mesh3D`. + /// Currently supports only sRGB(A) textures, ignoring alpha. + /// (meaning that the tensor must have 3 or 4 channels and use the `u8` format) + pub albedo_texture: Option, + /// Optional class Ids for the vertices. /// /// The class ID provides colors and labels if not specified explicitly. @@ -90,7 +100,9 @@ impl ::re_types_core::SizeBytes for Mesh3D { + self.mesh_properties.heap_size_bytes() + self.vertex_normals.heap_size_bytes() + self.vertex_colors.heap_size_bytes() + + self.vertex_texcoords.heap_size_bytes() + self.mesh_material.heap_size_bytes() + + self.albedo_texture.heap_size_bytes() + self.class_ids.heap_size_bytes() + self.instance_keys.heap_size_bytes() } @@ -101,7 +113,9 @@ impl ::re_types_core::SizeBytes for Mesh3D { && >::is_pod() && >>::is_pod() && >>::is_pod() + && >>::is_pod() && >::is_pod() + && >::is_pod() && >>::is_pod() && >>::is_pod() } @@ -119,17 +133,19 @@ static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = ] }); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 6usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.ClassId".into(), "rerun.components.Color".into(), "rerun.components.InstanceKey".into(), "rerun.components.Material".into(), + "rerun.components.TensorData".into(), + "rerun.components.Texcoord2D".into(), ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 10usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Position3D".into(), @@ -140,11 +156,13 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = "rerun.components.Color".into(), "rerun.components.InstanceKey".into(), "rerun.components.Material".into(), + "rerun.components.TensorData".into(), + "rerun.components.Texcoord2D".into(), ] }); impl Mesh3D { - pub const NUM_COMPONENTS: usize = 8usize; + pub const NUM_COMPONENTS: usize = 10usize; } /// Indicator component for the [`Mesh3D`] [`::re_types_core::Archetype`] @@ -240,6 +258,19 @@ impl ::re_types_core::Archetype for Mesh3D { } else { None }; + let vertex_texcoords = + if let Some(array) = arrays_by_name.get("rerun.components.Texcoord2D") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Mesh3D#vertex_texcoords")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Mesh3D#vertex_texcoords")? + }) + } else { + None + }; let mesh_material = if let Some(array) = arrays_by_name.get("rerun.components.Material") { ::from_arrow_opt(&**array) .with_context("rerun.archetypes.Mesh3D#mesh_material")? @@ -249,6 +280,16 @@ impl ::re_types_core::Archetype for Mesh3D { } else { None }; + let albedo_texture = if let Some(array) = arrays_by_name.get("rerun.components.TensorData") + { + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Mesh3D#albedo_texture")? + .into_iter() + .next() + .flatten() + } else { + None + }; let class_ids = if let Some(array) = arrays_by_name.get("rerun.components.ClassId") { Some({ ::from_arrow_opt(&**array) @@ -279,7 +320,9 @@ impl ::re_types_core::Archetype for Mesh3D { mesh_properties, vertex_normals, vertex_colors, + vertex_texcoords, mesh_material, + albedo_texture, class_ids, instance_keys, }) @@ -302,9 +345,15 @@ impl ::re_types_core::AsComponents for Mesh3D { self.vertex_colors .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.vertex_texcoords + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), self.mesh_material .as_ref() .map(|comp| (comp as &dyn ComponentBatch).into()), + self.albedo_texture + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), self.class_ids .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), @@ -332,7 +381,9 @@ impl Mesh3D { mesh_properties: None, vertex_normals: None, vertex_colors: None, + vertex_texcoords: None, mesh_material: None, + albedo_texture: None, class_ids: None, instance_keys: None, } @@ -365,6 +416,15 @@ impl Mesh3D { self } + #[inline] + pub fn with_vertex_texcoords( + mut self, + vertex_texcoords: impl IntoIterator>, + ) -> Self { + self.vertex_texcoords = Some(vertex_texcoords.into_iter().map(Into::into).collect()); + self + } + #[inline] pub fn with_mesh_material( mut self, @@ -374,6 +434,15 @@ impl Mesh3D { self } + #[inline] + pub fn with_albedo_texture( + mut self, + albedo_texture: impl Into, + ) -> Self { + self.albedo_texture = Some(albedo_texture.into()); + self + } + #[inline] pub fn with_class_ids( mut self, diff --git a/crates/re_types/src/components/.gitattributes b/crates/re_types/src/components/.gitattributes index 501c9e3b69b4..03fe0fe129f6 100644 --- a/crates/re_types/src/components/.gitattributes +++ b/crates/re_types/src/components/.gitattributes @@ -27,6 +27,7 @@ rotation3d.rs linguist-generated=true scalar.rs linguist-generated=true scalar_scattering.rs linguist-generated=true tensor_data.rs linguist-generated=true +texcoord2d.rs linguist-generated=true text.rs linguist-generated=true text_log_level.rs linguist-generated=true transform3d.rs linguist-generated=true diff --git a/crates/re_types/src/components/mod.rs b/crates/re_types/src/components/mod.rs index 6a3917e36be3..3ca7004b8a4a 100644 --- a/crates/re_types/src/components/mod.rs +++ b/crates/re_types/src/components/mod.rs @@ -45,6 +45,8 @@ mod scalar; mod scalar_ext; mod scalar_scattering; mod tensor_data; +mod texcoord2d; +mod texcoord2d_ext; mod text; mod text_ext; mod text_log_level; @@ -83,6 +85,7 @@ pub use self::rotation3d::Rotation3D; pub use self::scalar::Scalar; pub use self::scalar_scattering::ScalarScattering; pub use self::tensor_data::TensorData; +pub use self::texcoord2d::Texcoord2D; pub use self::text::Text; pub use self::text_log_level::TextLogLevel; pub use self::transform3d::Transform3D; diff --git a/crates/re_types/src/components/texcoord2d.rs b/crates/re_types/src/components/texcoord2d.rs new file mode 100644 index 000000000000..ef88b526d9d4 --- /dev/null +++ b/crates/re_types/src/components/texcoord2d.rs @@ -0,0 +1,316 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/re_types/definitions/rerun/components/texcoord2d.fbs". + +#![allow(trivial_numeric_casts)] +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::iter_on_single_items)] +#![allow(clippy::map_flatten)] +#![allow(clippy::match_wildcard_for_single_variants)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::unnecessary_cast)] + +use ::re_types_core::external::arrow2; +use ::re_types_core::ComponentName; +use ::re_types_core::SerializationResult; +use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch}; +use ::re_types_core::{DeserializationError, DeserializationResult}; + +/// **Component**: A 2D texture UV coordinate. +/// +/// Texture coordinates specify a position on a 2D texture. +/// A range from 0-1 covers the entire texture in the respective dimension. +/// Unless configured otherwise, the texture repeats outside of this range. +/// Rerun uses top-left as the origin for UV coordinates. +/// +/// 0 U 1 +/// 0 + --------- → +/// | . +/// V | . +/// | . +/// 1 ↓ . . . . . . +/// +/// This is the same convention as in Vulkan/Metal/DX12/WebGPU, but (!) unlike OpenGL, +/// which places the origin at the bottom-left. +#[derive(Clone, Debug, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct Texcoord2D(pub crate::datatypes::Vec2D); + +impl ::re_types_core::SizeBytes for Texcoord2D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for Texcoord2D { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for Texcoord2D { + #[inline] + fn borrow(&self) -> &crate::datatypes::Vec2D { + &self.0 + } +} + +impl std::ops::Deref for Texcoord2D { + type Target = crate::datatypes::Vec2D; + + #[inline] + fn deref(&self) -> &crate::datatypes::Vec2D { + &self.0 + } +} + +::re_types_core::macros::impl_into_cow!(Texcoord2D); + +impl ::re_types_core::Loggable for Texcoord2D { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.Texcoord2D".into() + } + + #[allow(clippy::wildcard_imports)] + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + use arrow2::datatypes::*; + DataType::FixedSizeList( + std::sync::Arc::new(Field { + name: "item".to_owned(), + data_type: DataType::Float32, + is_nullable: false, + metadata: [].into(), + }), + 2usize, + ) + } + + #[allow(clippy::wildcard_imports)] + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, datatypes::*}; + Ok({ + let (somes, data0): (Vec<_>, Vec<_>) = data + .into_iter() + .map(|datum| { + let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into); + let datum = datum.map(|datum| { + let Self(data0) = datum.into_owned(); + data0 + }); + (datum.is_some(), datum) + }) + .unzip(); + let data0_bitmap: Option = { + let any_nones = somes.iter().any(|some| !*some); + any_nones.then(|| somes.into()) + }; + { + use arrow2::{buffer::Buffer, offset::OffsetsBuffer}; + let data0_inner_data: Vec<_> = data0 + .iter() + .map(|datum| { + datum + .map(|datum| { + let crate::datatypes::Vec2D(data0) = datum; + data0 + }) + .unwrap_or_default() + }) + .flatten() + .map(Some) + .collect(); + let data0_inner_bitmap: Option = + data0_bitmap.as_ref().map(|bitmap| { + bitmap + .iter() + .map(|i| std::iter::repeat(i).take(2usize)) + .flatten() + .collect::>() + .into() + }); + FixedSizeListArray::new( + Self::arrow_datatype(), + PrimitiveArray::new( + DataType::Float32, + data0_inner_data + .into_iter() + .map(|v| v.unwrap_or_default()) + .collect(), + data0_inner_bitmap, + ) + .boxed(), + data0_bitmap, + ) + .boxed() + } + }) + } + + #[allow(clippy::wildcard_imports)] + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, buffer::*, datatypes::*}; + Ok({ + let arrow_data = arrow_data + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::FixedSizeList( + std::sync::Arc::new(Field { + name: "item".to_owned(), + data_type: DataType::Float32, + is_nullable: false, + metadata: [].into(), + }), + 2usize, + ), + arrow_data.data_type().clone(), + ) + }) + .with_context("rerun.components.Texcoord2D#uv")?; + if arrow_data.is_empty() { + Vec::new() + } else { + let offsets = (0..) + .step_by(2usize) + .zip((2usize..).step_by(2usize).take(arrow_data.len())); + let arrow_data_inner = { + let arrow_data_inner = &**arrow_data.values(); + arrow_data_inner + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::Float32, + arrow_data_inner.data_type().clone(), + ) + }) + .with_context("rerun.components.Texcoord2D#uv")? + .into_iter() + .map(|opt| opt.copied()) + .collect::>() + }; + arrow2::bitmap::utils::ZipValidity::new_with_validity( + offsets, + arrow_data.validity(), + ) + .map(|elem| { + elem.map(|(start, end)| { + debug_assert!(end - start == 2usize); + if end as usize > arrow_data_inner.len() { + return Err(DeserializationError::offset_slice_oob( + (start, end), + arrow_data_inner.len(), + )); + } + + #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)] + let data = + unsafe { arrow_data_inner.get_unchecked(start as usize..end as usize) }; + let data = data.iter().cloned().map(Option::unwrap_or_default); + let arr = array_init::from_iter(data).unwrap(); + Ok(arr) + }) + .transpose() + }) + .map(|res_or_opt| { + res_or_opt.map(|res_or_opt| res_or_opt.map(|v| crate::datatypes::Vec2D(v))) + }) + .collect::>>>()? + } + .into_iter() + } + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .map(|res| res.map(|v| Some(Self(v)))) + .collect::>>>() + .with_context("rerun.components.Texcoord2D#uv") + .with_context("rerun.components.Texcoord2D")?) + } + + #[allow(clippy::wildcard_imports)] + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, buffer::*, datatypes::*}; + if let Some(validity) = arrow_data.validity() { + if validity.unset_bits() != 0 { + return Err(DeserializationError::missing_data()); + } + } + Ok({ + let slice = { + let arrow_data = arrow_data + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::FixedSizeList( + std::sync::Arc::new(Field { + name: "item".to_owned(), + data_type: DataType::Float32, + is_nullable: false, + metadata: [].into(), + }), + 2usize, + ), + arrow_data.data_type().clone(), + ) + }) + .with_context("rerun.components.Texcoord2D#uv")?; + let arrow_data_inner = &**arrow_data.values(); + bytemuck::cast_slice::<_, [_; 2usize]>( + arrow_data_inner + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::Float32, + arrow_data_inner.data_type().clone(), + ) + }) + .with_context("rerun.components.Texcoord2D#uv")? + .values() + .as_slice(), + ) + }; + { + slice + .iter() + .copied() + .map(|v| crate::datatypes::Vec2D(v)) + .map(|v| Self(v)) + .collect::>() + } + }) + } +} diff --git a/crates/re_types/src/components/texcoord2d_ext.rs b/crates/re_types/src/components/texcoord2d_ext.rs new file mode 100644 index 000000000000..653cb76762d5 --- /dev/null +++ b/crates/re_types/src/components/texcoord2d_ext.rs @@ -0,0 +1,52 @@ +use crate::datatypes::Vec2D; + +use super::Texcoord2D; + +// --- + +impl Texcoord2D { + pub const ZERO: Self = Self::new(0.0, 0.0); + pub const ONE: Self = Self::new(1.0, 1.0); + + #[inline] + pub const fn new(u: f32, v: f32) -> Self { + Self(Vec2D::new(u, v)) + } + + #[inline] + pub fn u(&self) -> f32 { + self.0.x() + } + + #[inline] + pub fn v(&self) -> f32 { + self.0.y() + } +} + +#[cfg(feature = "glam")] +impl From for glam::Vec2 { + #[inline] + fn from(pt: Texcoord2D) -> Self { + Self::new(pt.u(), pt.v()) + } +} + +#[cfg(feature = "mint")] +impl From for mint::Point2 { + #[inline] + fn from(position: Texcoord2D) -> Self { + Self { + x: position.u(), + y: position.v(), + } + } +} + +#[cfg(feature = "mint")] +impl From> for Texcoord2D { + #[inline] + fn from(position: mint::Point2) -> Self { + Self(Vec2D([position.x, position.y])) + } +} diff --git a/crates/re_types/src/datatypes/rgba32_ext.rs b/crates/re_types/src/datatypes/rgba32_ext.rs index 6e3f7df89b88..da1594148e85 100644 --- a/crates/re_types/src/datatypes/rgba32_ext.rs +++ b/crates/re_types/src/datatypes/rgba32_ext.rs @@ -1,26 +1,28 @@ use super::Rgba32; impl Rgba32 { + pub const WHITE: Self = Self::from_rgb(255, 255, 255); + #[inline] - pub fn from_rgb(r: u8, g: u8, b: u8) -> Self { - Self::from([r, g, b, 255]) + pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Self::from_unmultiplied_rgba(r, g, b, 255) } #[inline] - pub fn from_unmultiplied_rgba(r: u8, g: u8, b: u8, a: u8) -> Self { + pub const fn from_unmultiplied_rgba(r: u8, g: u8, b: u8, a: u8) -> Self { let [r, g, b, a] = [r as u32, g as u32, b as u32, a as u32]; Self(r << 24 | g << 16 | b << 8 | a) } /// Most significant byte is `r`, least significant byte is `a`. #[inline] - pub fn from_u32(rgba: u32) -> Self { + pub const fn from_u32(rgba: u32) -> Self { Self(rgba) } /// `[r, g, b, a]` #[inline] - pub fn to_array(self) -> [u8; 4] { + pub const fn to_array(self) -> [u8; 4] { [ (self.0 >> 24) as u8, (self.0 >> 16) as u8, @@ -31,7 +33,7 @@ impl Rgba32 { /// Most significant byte is `r`, least significant byte is `a`. #[inline] - pub fn to_u32(self) -> u32 { + pub const fn to_u32(self) -> u32 { self.0 } } diff --git a/crates/re_types/tests/mesh3d.rs b/crates/re_types/tests/mesh3d.rs index faf1218ab604..be2c52c842df 100644 --- a/crates/re_types/tests/mesh3d.rs +++ b/crates/re_types/tests/mesh3d.rs @@ -2,13 +2,30 @@ use std::collections::HashMap; use re_types::{ archetypes::Mesh3D, - components::{ClassId, InstanceKey, Position3D, Vector3D}, - datatypes::{Material, MeshProperties, Rgba32, Vec3D}, + components::{ClassId, InstanceKey, Position3D, Texcoord2D, Vector3D}, + datatypes::{ + Material, MeshProperties, Rgba32, TensorBuffer, TensorData, TensorDimension, Vec2D, Vec3D, + }, Archetype as _, AsComponents as _, }; #[test] fn roundtrip() { + let tensor_data: re_types::components::TensorData = TensorData { + shape: vec![ + TensorDimension { + size: 2, + name: Some("height".into()), + }, + TensorDimension { + size: 3, + name: Some("width".into()), + }, + ], + buffer: TensorBuffer::U8(vec![1, 2, 3, 4, 5, 6].into()), + } + .into(); + let expected = Mesh3D { vertex_positions: vec![ Position3D(Vec3D([1.0, 2.0, 3.0])), @@ -28,12 +45,17 @@ fn roundtrip() { Rgba32::from_unmultiplied_rgba(0xAA, 0x00, 0x00, 0xCC).into(), // Rgba32::from_unmultiplied_rgba(0x00, 0xBB, 0x00, 0xDD).into(), ]), + vertex_texcoords: Some(vec![ + Texcoord2D(Vec2D([0.0, 1.0])), // + Texcoord2D(Vec2D([2.0, 3.0])), // + ]), mesh_material: Some( Material { albedo_factor: Some(Rgba32::from_unmultiplied_rgba(0xEE, 0x11, 0x22, 0x33)), } .into(), ), + albedo_texture: Some(tensor_data.clone()), class_ids: Some(vec![ ClassId::from(126), // ClassId::from(127), // @@ -51,9 +73,11 @@ fn roundtrip() { ])) .with_vertex_normals([[4.0, 5.0, 6.0], [40.0, 50.0, 60.0]]) .with_vertex_colors([0xAA0000CC, 0x00BB00DD]) + .with_vertex_texcoords([[0.0, 1.0], [2.0, 3.0]]) .with_mesh_material(Material::from_albedo_factor(0xEE112233)) .with_class_ids([126, 127]) - .with_instance_keys([u64::MAX - 1, u64::MAX]); + .with_instance_keys([u64::MAX - 1, u64::MAX]) + .with_albedo_texture(tensor_data); similar_asserts::assert_eq!(expected, arch); let expected_extensions: HashMap<_, _> = [ diff --git a/crates/re_viewer_context/src/gpu_bridge/mod.rs b/crates/re_viewer_context/src/gpu_bridge/mod.rs index 2c14f20c19b5..55adf3a1d9be 100644 --- a/crates/re_viewer_context/src/gpu_bridge/mod.rs +++ b/crates/re_viewer_context/src/gpu_bridge/mod.rs @@ -8,6 +8,7 @@ pub use colormap::colormap_dropdown_button_ui; pub use re_renderer_callback::new_renderer_callback; pub use tensor_to_gpu::{ class_id_tensor_to_gpu, color_tensor_to_gpu, depth_tensor_to_gpu, tensor_to_gpu, + texture_height_width_channels, }; use crate::TensorStats; diff --git a/crates/re_viewer_context/src/gpu_bridge/tensor_to_gpu.rs b/crates/re_viewer_context/src/gpu_bridge/tensor_to_gpu.rs index 8e6f16b16380..174e7624a218 100644 --- a/crates/re_viewer_context/src/gpu_bridge/tensor_to_gpu.rs +++ b/crates/re_viewer_context/src/gpu_bridge/tensor_to_gpu.rs @@ -545,11 +545,11 @@ fn pad_and_narrow_and_cast( // ----------------------------------------------------------------------------; -fn texture_height_width_channels(tensor: &TensorData) -> anyhow::Result<[u32; 3]> { +pub fn texture_height_width_channels(tensor: &TensorData) -> anyhow::Result<[u32; 3]> { use anyhow::Context as _; let Some([mut height, mut width, channel]) = tensor.image_height_width_channels() else { - anyhow::bail!("Tensor is not an image"); + anyhow::bail!("Tensor with shape {:?} is not an image", tensor.shape); }; height = match tensor.buffer { // Correct the texture height for NV12, tensor.image_height_width_channels returns the RGB size for NV12 images. diff --git a/docs/content/reference/types/archetypes/mesh3d.md b/docs/content/reference/types/archetypes/mesh3d.md index 627bf593d275..3c76f1233bf2 100644 --- a/docs/content/reference/types/archetypes/mesh3d.md +++ b/docs/content/reference/types/archetypes/mesh3d.md @@ -10,7 +10,7 @@ A 3D triangle mesh as specified by its per-mesh and per-vertex properties. **Recommended**: [`MeshProperties`](../components/mesh_properties.md), [`Vector3D`](../components/vector3d.md) -**Optional**: [`Color`](../components/color.md), [`Material`](../components/material.md), [`ClassId`](../components/class_id.md), [`InstanceKey`](../components/instance_key.md) +**Optional**: [`Color`](../components/color.md), [`Texcoord2D`](../components/texcoord2d.md), [`Material`](../components/material.md), [`TensorData`](../components/tensor_data.md), [`ClassId`](../components/class_id.md), [`InstanceKey`](../components/instance_key.md) ## Links * 🌊 [C++ API docs for `Mesh3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1archetypes_1_1Mesh3D.html) diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index a2a5241d0321..7328f1e8b454 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -34,6 +34,7 @@ Archetypes are bundles of components * [`Scalar`](components/scalar.md) * [`ScalarScattering`](components/scalar_scattering.md) * [`TensorData`](components/tensor_data.md) +* [`Texcoord2D`](components/texcoord2d.md) * [`Text`](components/text.md) * [`TextLogLevel`](components/text_log_level.md) * [`Transform3D`](components/transform3d.md) diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index 9b6a72842029..cc952f0754e8 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -28,6 +28,7 @@ rotation3d.md linguist-generated=true scalar.md linguist-generated=true scalar_scattering.md linguist-generated=true tensor_data.md linguist-generated=true +texcoord2d.md linguist-generated=true text.md linguist-generated=true text_log_level.md linguist-generated=true transform3d.md linguist-generated=true diff --git a/docs/content/reference/types/components/tensor_data.md b/docs/content/reference/types/components/tensor_data.md index fb2a37846015..2895dec1081e 100644 --- a/docs/content/reference/types/components/tensor_data.md +++ b/docs/content/reference/types/components/tensor_data.md @@ -19,5 +19,6 @@ A multi-dimensional `Tensor` with optionally named arguments. * [`BarChart`](../archetypes/bar_chart.md) * [`DepthImage`](../archetypes/depth_image.md) * [`Image`](../archetypes/image.md) +* [`Mesh3D`](../archetypes/mesh3d.md) * [`SegmentationImage`](../archetypes/segmentation_image.md) * [`Tensor`](../archetypes/tensor.md) diff --git a/docs/content/reference/types/components/texcoord2d.md b/docs/content/reference/types/components/texcoord2d.md new file mode 100644 index 000000000000..caa918cedb17 --- /dev/null +++ b/docs/content/reference/types/components/texcoord2d.md @@ -0,0 +1,34 @@ +--- +title: "Texcoord2D" +--- + +A 2D texture UV coordinate. + +Texture coordinates specify a position on a 2D texture. +A range from 0-1 covers the entire texture in the respective dimension. +Unless configured otherwise, the texture repeats outside of this range. +Rerun uses top-left as the origin for UV coordinates. + + 0 U 1 +0 + --------- → + | . +V | . + | . +1 ↓ . . . . . . + +This is the same convention as in Vulkan/Metal/DX12/WebGPU, but (!) unlike OpenGL, +which places the origin at the bottom-left. + +## Fields + +* uv: [`Vec2D`](../datatypes/vec2d.md) + +## Links + * 🌊 [C++ API docs for `Texcoord2D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1Texcoord2D.html?speculative-link) + * 🐍 [Python API docs for `Texcoord2D`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.Texcoord2D) + * 🦀 [Rust API docs for `Texcoord2D`](https://docs.rs/rerun/latest/rerun/components/struct.Texcoord2D.html?speculative-link) + + +## Used by + +* [`Mesh3D`](../archetypes/mesh3d.md) diff --git a/docs/content/reference/types/datatypes/vec2d.md b/docs/content/reference/types/datatypes/vec2d.md index 54e549e1f44c..d49ab5101f06 100644 --- a/docs/content/reference/types/datatypes/vec2d.md +++ b/docs/content/reference/types/datatypes/vec2d.md @@ -17,4 +17,5 @@ A vector in 2D space. * [`LineStrip2D`](../components/line_strip2d.md) * [`Position2D`](../components/position2d.md) * [`Resolution`](../components/resolution.md) +* [`Texcoord2D`](../components/texcoord2d.md?speculative-link) * [`Vector2D`](../components/vector2d.md?speculative-link) diff --git a/docs/cspell.json b/docs/cspell.json index 8288ae0c1460..08788d347a5f 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -274,8 +274,8 @@ "ndarray", "nohash", "noqa", - "nuScenes", "numpy", + "nuScenes", "nyud", "obbs", "obj", @@ -351,6 +351,7 @@ "superquadrics", "tableofcontents", "taplo", + "Texcoord", "thiserror", "timepanel", "timepoint", diff --git a/examples/python/raw_mesh/main.py b/examples/python/raw_mesh/main.py index cb18d38f8dfa..5bfd677919fa 100755 --- a/examples/python/raw_mesh/main.py +++ b/examples/python/raw_mesh/main.py @@ -53,24 +53,43 @@ def log_scene(scene: trimesh.Scene, node: str, path: str | None = None) -> None: mesh = cast(trimesh.Trimesh, scene.geometry.get(node_data[1])) if mesh: vertex_colors = None + vertex_texcoords = None mesh_material = None + albedo_texture = None + try: - colors = mesh.visual.to_color().vertex_colors - if len(colors) == 4: - # If trimesh gives us a single vertex color for the entire mesh, we can interpret that - # as an albedo factor for the whole primitive. - mesh_material = Material(albedo_factor=np.array(colors)) - else: - vertex_colors = colors + vertex_texcoords = mesh.visual.uv + # trimesh uses the OpenGL convention for UV coordinates, so we need to flip the V coordinate + # since Rerun uses the Vulkan/Metal/DX12/WebGPU convention. + vertex_texcoords[:, 1] = 1.0 - vertex_texcoords[:, 1] except Exception: pass + try: + albedo_texture = mesh.visual.material.baseColorTexture + if mesh.visual.material.baseColorTexture is None: + raise ValueError() + except Exception: + # Try vertex colors instead. + try: + colors = mesh.visual.to_color().vertex_colors + if len(colors) == 4: + # If trimesh gives us a single vertex color for the entire mesh, we can interpret that + # as an albedo factor for the whole primitive. + mesh_material = Material(albedo_factor=np.array(colors)) + else: + vertex_colors = colors + except Exception: + pass + rr.log( path, rr.Mesh3D( vertex_positions=mesh.vertices, vertex_colors=vertex_colors, vertex_normals=mesh.vertex_normals, + vertex_texcoords=vertex_texcoords, + albedo_texture=albedo_texture, indices=mesh.faces, mesh_material=mesh_material, ), diff --git a/examples/rust/raw_mesh/src/main.rs b/examples/rust/raw_mesh/src/main.rs index 2d99210853bf..dc40f77cd18f 100644 --- a/examples/rust/raw_mesh/src/main.rs +++ b/examples/rust/raw_mesh/src/main.rs @@ -31,7 +31,7 @@ impl From for Mesh3D { vertex_positions, vertex_colors, vertex_normals, - vertex_texcoords: _, // TODO(cmc): support mesh texturing + vertex_texcoords, } = primitive; let mut mesh = Mesh3D::new(vertex_positions); @@ -48,6 +48,9 @@ impl From for Mesh3D { if let Some(vertex_colors) = vertex_colors { mesh = mesh.with_vertex_colors(vertex_colors); } + if let Some(vertex_texcoords) = vertex_texcoords { + mesh = mesh.with_vertex_texcoords(vertex_texcoords); + } if albedo_factor.is_some() { mesh = mesh.with_mesh_material(rerun::datatypes::Material { albedo_factor: albedo_factor @@ -269,6 +272,8 @@ fn node_primitives<'data>( let vertex_texcoords = reader.read_tex_coords(0); // TODO(cmc): pick correct set let vertex_texcoords = vertex_texcoords.map(|texcoords| texcoords.into_f32().collect()); + // TODO(cmc): support for albedo textures + GltfPrimitive { albedo_factor, vertex_positions, diff --git a/rerun_cpp/src/rerun/archetypes/mesh3d.cpp b/rerun_cpp/src/rerun/archetypes/mesh3d.cpp index c3e0a07429d2..7d774e5acefe 100644 --- a/rerun_cpp/src/rerun/archetypes/mesh3d.cpp +++ b/rerun_cpp/src/rerun/archetypes/mesh3d.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(8); + cells.reserve(10); { auto result = DataCell::from_loggable(archetype.vertex_positions); @@ -36,11 +36,21 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.vertex_texcoords.has_value()) { + auto result = DataCell::from_loggable(archetype.vertex_texcoords.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.mesh_material.has_value()) { auto result = DataCell::from_loggable(archetype.mesh_material.value()); RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.albedo_texture.has_value()) { + auto result = DataCell::from_loggable(archetype.albedo_texture.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.class_ids.has_value()) { auto result = DataCell::from_loggable(archetype.class_ids.value()); RR_RETURN_NOT_OK(result.error); diff --git a/rerun_cpp/src/rerun/archetypes/mesh3d.hpp b/rerun_cpp/src/rerun/archetypes/mesh3d.hpp index b19b9b91809e..e74daa9f7691 100644 --- a/rerun_cpp/src/rerun/archetypes/mesh3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/mesh3d.hpp @@ -11,6 +11,8 @@ #include "../components/material.hpp" #include "../components/mesh_properties.hpp" #include "../components/position3d.hpp" +#include "../components/tensor_data.hpp" +#include "../components/texcoord2d.hpp" #include "../components/vector3d.hpp" #include "../data_cell.hpp" #include "../indicator_component.hpp" @@ -77,9 +79,19 @@ namespace rerun::archetypes { /// An optional color for each vertex. std::optional> vertex_colors; + /// An optional uv texture coordinate for each vertex. + std::optional> vertex_texcoords; + /// Optional material properties for the mesh as a whole. std::optional mesh_material; + /// Optional albedo texture. + /// + /// Used with `vertex_texcoords` on `Mesh3D`. + /// Currently supports only sRGB(A) textures, ignoring alpha. + /// (meaning that the tensor must have 3 or 4 channels and use the `u8` format) + std::optional albedo_texture; + /// Optional class Ids for the vertices. /// /// The class ID provides colors and labels if not specified explicitly. @@ -124,6 +136,14 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// An optional uv texture coordinate for each vertex. + Mesh3D with_vertex_texcoords(Collection _vertex_texcoords + ) && { + vertex_texcoords = std::move(_vertex_texcoords); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + /// Optional material properties for the mesh as a whole. Mesh3D with_mesh_material(rerun::components::Material _mesh_material) && { mesh_material = std::move(_mesh_material); @@ -131,6 +151,17 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// Optional albedo texture. + /// + /// Used with `vertex_texcoords` on `Mesh3D`. + /// Currently supports only sRGB(A) textures, ignoring alpha. + /// (meaning that the tensor must have 3 or 4 channels and use the `u8` format) + Mesh3D with_albedo_texture(rerun::components::TensorData _albedo_texture) && { + albedo_texture = std::move(_albedo_texture); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + /// Optional class Ids for the vertices. /// /// The class ID provides colors and labels if not specified explicitly. diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index 64467627d132..46d5dd7d0229 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -29,6 +29,7 @@ #include "components/scalar.hpp" #include "components/scalar_scattering.hpp" #include "components/tensor_data.hpp" +#include "components/texcoord2d.hpp" #include "components/text.hpp" #include "components/text_log_level.hpp" #include "components/transform3d.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index e6c7f1cd25f3..05d27bfe2e0d 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -55,6 +55,8 @@ scalar_scattering.cpp linguist-generated=true scalar_scattering.hpp linguist-generated=true tensor_data.cpp linguist-generated=true tensor_data.hpp linguist-generated=true +texcoord2d.cpp linguist-generated=true +texcoord2d.hpp linguist-generated=true text.cpp linguist-generated=true text.hpp linguist-generated=true text_log_level.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/texcoord2d.cpp b/rerun_cpp/src/rerun/components/texcoord2d.cpp new file mode 100644 index 000000000000..b462646d8168 --- /dev/null +++ b/rerun_cpp/src/rerun/components/texcoord2d.cpp @@ -0,0 +1,52 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/components/texcoord2d.fbs". + +#include "texcoord2d.hpp" + +#include "../datatypes/vec2d.hpp" + +#include +#include + +namespace rerun::components {} + +namespace rerun { + const std::shared_ptr& Loggable::arrow_datatype() { + static const auto datatype = Loggable::arrow_datatype(); + return datatype; + } + + rerun::Error Loggable::fill_arrow_array_builder( + arrow::FixedSizeListBuilder* builder, const components::Texcoord2D* elements, + size_t num_elements + ) { + static_assert(sizeof(rerun::datatypes::Vec2D) == sizeof(components::Texcoord2D)); + RR_RETURN_NOT_OK(Loggable::fill_arrow_array_builder( + builder, + reinterpret_cast(elements), + num_elements + )); + + return Error::ok(); + } + + Result> Loggable::to_arrow( + const components::Texcoord2D* instances, size_t num_instances + ) { + // TODO(andreas): Allow configuring the memory pool. + arrow::MemoryPool* pool = arrow::default_memory_pool(); + auto datatype = arrow_datatype(); + + ARROW_ASSIGN_OR_RAISE(auto builder, arrow::MakeBuilder(datatype, pool)) + if (instances && num_instances > 0) { + RR_RETURN_NOT_OK(Loggable::fill_arrow_array_builder( + static_cast(builder.get()), + instances, + num_instances + )); + } + std::shared_ptr array; + ARROW_RETURN_NOT_OK(builder->Finish(&array)); + return array; + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/texcoord2d.hpp b/rerun_cpp/src/rerun/components/texcoord2d.hpp new file mode 100644 index 000000000000..34101cec6967 --- /dev/null +++ b/rerun_cpp/src/rerun/components/texcoord2d.hpp @@ -0,0 +1,100 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/components/texcoord2d.fbs". + +#pragma once + +#include "../datatypes/vec2d.hpp" +#include "../result.hpp" + +#include +#include +#include + +namespace arrow { + class Array; + class DataType; + class FixedSizeListBuilder; +} // namespace arrow + +namespace rerun::components { + /// **Component**: A 2D texture UV coordinate. + /// + /// Texture coordinates specify a position on a 2D texture. + /// A range from 0-1 covers the entire texture in the respective dimension. + /// Unless configured otherwise, the texture repeats outside of this range. + /// Rerun uses top-left as the origin for UV coordinates. + /// + /// 0 U 1 + /// 0 + --------- → + /// | . + /// V | . + /// | . + /// 1 ↓ . . . . . . + /// + /// This is the same convention as in Vulkan/Metal/DX12/WebGPU, but (!) unlike OpenGL, + /// which places the origin at the bottom-left. + struct Texcoord2D { + rerun::datatypes::Vec2D uv; + + public: + // Extensions to generated type defined in 'texcoord2d_ext.cpp' + + /// Construct Texcoord2D from u/v values. + Texcoord2D(float u, float v) : uv{u, v} {} + + float u() const { + return uv.x(); + } + + float v() const { + return uv.y(); + } + + public: + Texcoord2D() = default; + + Texcoord2D(rerun::datatypes::Vec2D uv_) : uv(uv_) {} + + Texcoord2D& operator=(rerun::datatypes::Vec2D uv_) { + uv = uv_; + return *this; + } + + Texcoord2D(std::array xy_) : uv(xy_) {} + + Texcoord2D& operator=(std::array xy_) { + uv = xy_; + return *this; + } + + /// Cast to the underlying Vec2D datatype + operator rerun::datatypes::Vec2D() const { + return uv; + } + }; +} // namespace rerun::components + +namespace rerun { + template + struct Loggable; + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.Texcoord2D"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype(); + + /// Fills an arrow array builder with an array of this type. + static rerun::Error fill_arrow_array_builder( + arrow::FixedSizeListBuilder* builder, const components::Texcoord2D* elements, + size_t num_elements + ); + + /// Serializes an array of `rerun::components::Texcoord2D` into an arrow array. + static Result> to_arrow( + const components::Texcoord2D* instances, size_t num_instances + ); + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/texcoord2d_ext.cpp b/rerun_cpp/src/rerun/components/texcoord2d_ext.cpp new file mode 100644 index 000000000000..0fd79fd78e93 --- /dev/null +++ b/rerun_cpp/src/rerun/components/texcoord2d_ext.cpp @@ -0,0 +1,27 @@ +#include "texcoord2d.hpp" + +// Uncomment for better auto-complete while editing the extension. +// #define EDIT_EXTENSION + +namespace rerun { + namespace components { + +#ifdef EDIT_EXTENSION + // + + /// Construct Texcoord2D from u/v values. + Texcoord2D(float u, float v) : uv{u, v} {} + + float u() const { + return uv.x(); + } + + float v() const { + return uv.y(); + } + + // + }; +#endif +} // namespace components +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py index 79eb8c3b9e3d..b68b2acd52db 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py @@ -60,7 +60,9 @@ def __attrs_clear__(self) -> None: mesh_properties=None, # type: ignore[arg-type] vertex_normals=None, # type: ignore[arg-type] vertex_colors=None, # type: ignore[arg-type] + vertex_texcoords=None, # type: ignore[arg-type] mesh_material=None, # type: ignore[arg-type] + albedo_texture=None, # type: ignore[arg-type] class_ids=None, # type: ignore[arg-type] instance_keys=None, # type: ignore[arg-type] ) @@ -111,6 +113,15 @@ def _clear(cls) -> Mesh3D: # # (Docstring intentionally commented out to hide this field from the docs) + vertex_texcoords: components.Texcoord2DBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.Texcoord2DBatch._optional, # type: ignore[misc] + ) + # An optional uv texture coordinate for each vertex. + # + # (Docstring intentionally commented out to hide this field from the docs) + mesh_material: components.MaterialBatch | None = field( metadata={"component": "optional"}, default=None, @@ -120,6 +131,19 @@ def _clear(cls) -> Mesh3D: # # (Docstring intentionally commented out to hide this field from the docs) + albedo_texture: components.TensorDataBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.TensorDataBatch._optional, # type: ignore[misc] + ) + # Optional albedo texture. + # + # Used with `vertex_texcoords` on `Mesh3D`. + # Currently supports only sRGB(A) textures, ignoring alpha. + # (meaning that the tensor must have 3 or 4 channels and use the `u8` format) + # + # (Docstring intentionally commented out to hide this field from the docs) + class_ids: components.ClassIdBatch | None = field( metadata={"component": "optional"}, default=None, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py index dfd0480fc159..985d76971e0c 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d_ext.py @@ -19,6 +19,8 @@ def __init__( mesh_properties: datatypes.MeshPropertiesLike | None = None, vertex_normals: datatypes.Vec3DArrayLike | None = None, vertex_colors: datatypes.Rgba32ArrayLike | None = None, + vertex_texcoords: datatypes.Vec2DArrayLike | None = None, + albedo_texture: datatypes.TensorDataLike | None = None, mesh_material: datatypes.MaterialLike | None = None, class_ids: datatypes.ClassIdArrayLike | None = None, instance_keys: components.InstanceKeyArrayLike | None = None, @@ -41,10 +43,17 @@ def __init__( vertex_normals: An optional normal for each vertex. If specified, this must have as many elements as `vertex_positions`. + vertex_texcoords: + An optional texture coordinate for each vertex. + If specified, this must have as many elements as `vertex_positions`. vertex_colors: An optional color for each vertex. mesh_material: Optional material properties for the mesh as a whole. + albedo_texture: + Optional albedo texture. Used with `vertex_texcoords` on `Mesh3D`. + Currently supports only sRGB(A) textures, ignoring alpha. + (meaning that the tensor must have 3 or 4 channels and use the `u8` format) class_ids: Optional class Ids for the vertices. The class ID provides colors and labels if not specified explicitly. @@ -63,6 +72,8 @@ def __init__( mesh_properties=mesh_properties, vertex_normals=vertex_normals, vertex_colors=vertex_colors, + vertex_texcoords=vertex_texcoords, + albedo_texture=albedo_texture, mesh_material=mesh_material, class_ids=class_ids, instance_keys=instance_keys, diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index e695f39d550c..7a1379b36739 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -29,6 +29,7 @@ rotation3d.py linguist-generated=true scalar.py linguist-generated=true scalar_scattering.py linguist-generated=true tensor_data.py linguist-generated=true +texcoord2d.py linguist-generated=true text.py linguist-generated=true text_log_level.py linguist-generated=true transform3d.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/components/__init__.py b/rerun_py/rerun_sdk/rerun/components/__init__.py index 914c0f82a3dc..50b3453207f0 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -53,6 +53,7 @@ ScalarScatteringType, ) from .tensor_data import TensorData, TensorDataBatch, TensorDataType +from .texcoord2d import Texcoord2D, Texcoord2DBatch, Texcoord2DType from .text import Text, TextBatch, TextType from .text_log_level import TextLogLevel, TextLogLevelBatch, TextLogLevelType from .transform3d import Transform3D, Transform3DBatch, Transform3DType @@ -172,6 +173,9 @@ "TensorData", "TensorDataBatch", "TensorDataType", + "Texcoord2D", + "Texcoord2DBatch", + "Texcoord2DType", "Text", "TextBatch", "TextLogLevel", diff --git a/rerun_py/rerun_sdk/rerun/components/texcoord2d.py b/rerun_py/rerun_sdk/rerun/components/texcoord2d.py new file mode 100644 index 000000000000..5674f0f59df4 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/texcoord2d.py @@ -0,0 +1,45 @@ +# DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/python.rs +# Based on "crates/re_types/definitions/rerun/components/texcoord2d.fbs". + +# You can extend this class by creating a "Texcoord2DExt" class in "texcoord2d_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ComponentBatchMixin + +__all__ = ["Texcoord2D", "Texcoord2DBatch", "Texcoord2DType"] + + +class Texcoord2D(datatypes.Vec2D): + """ + **Component**: A 2D texture UV coordinate. + + Texture coordinates specify a position on a 2D texture. + A range from 0-1 covers the entire texture in the respective dimension. + Unless configured otherwise, the texture repeats outside of this range. + Rerun uses top-left as the origin for UV coordinates. + + 0 U 1 + 0 + --------- → + | . + V | . + | . + 1 ↓ . . . . . . + + This is the same convention as in Vulkan/Metal/DX12/WebGPU, but (!) unlike OpenGL, + which places the origin at the bottom-left. + """ + + # You can define your own __init__ function as a member of Texcoord2DExt in texcoord2d_ext.py + + # Note: there are no fields here because Texcoord2D delegates to datatypes.Vec2D + pass + + +class Texcoord2DType(datatypes.Vec2DType): + _TYPE_NAME: str = "rerun.components.Texcoord2D" + + +class Texcoord2DBatch(datatypes.Vec2DBatch, ComponentBatchMixin): + _ARROW_TYPE = Texcoord2DType() diff --git a/rerun_py/tests/unit/test_mesh3d.py b/rerun_py/tests/unit/test_mesh3d.py index 0fa3492f737d..c7a9ca071674 100644 --- a/rerun_py/tests/unit/test_mesh3d.py +++ b/rerun_py/tests/unit/test_mesh3d.py @@ -5,6 +5,7 @@ import rerun as rr from rerun.components import InstanceKeyArrayLike, MaterialBatch, MeshPropertiesBatch, Position3DBatch, Vector3DBatch +from rerun.components.texcoord2d import Texcoord2DBatch from rerun.datatypes import ( ClassIdArrayLike, Material, @@ -12,6 +13,7 @@ MeshProperties, MeshPropertiesLike, Rgba32ArrayLike, + Vec2DArrayLike, Vec3DArrayLike, ) @@ -23,6 +25,8 @@ instance_keys_arrays, instance_keys_expected, none_empty_or_value, + vec2ds_arrays, + vec2ds_expected, vec3ds_arrays, vec3ds_expected, ) @@ -55,11 +59,13 @@ def test_mesh3d() -> None: vertex_positions_arrays = vec3ds_arrays vertex_normals_arrays = vec3ds_arrays vertex_colors_arrays = colors_arrays + vertex_texcoord_arrays = vec2ds_arrays all_arrays = itertools.zip_longest( vertex_positions_arrays, vertex_normals_arrays, vertex_colors_arrays, + vertex_texcoord_arrays, mesh_properties_objects, mesh_materials, class_ids_arrays, @@ -70,6 +76,7 @@ def test_mesh3d() -> None: vertex_positions, vertex_normals, vertex_colors, + vertex_texcoords, mesh_properties, mesh_material, class_ids, @@ -81,6 +88,7 @@ def test_mesh3d() -> None: vertex_positions = cast(Vec3DArrayLike, vertex_positions) vertex_normals = cast(Optional[Vec3DArrayLike], vertex_normals) vertex_colors = cast(Optional[Rgba32ArrayLike], vertex_colors) + vertex_texcoords = cast(Optional[Vec2DArrayLike], vertex_texcoords) mesh_properties = cast(Optional[MeshPropertiesLike], mesh_properties) mesh_material = cast(Optional[MaterialLike], mesh_material) class_ids = cast(Optional[ClassIdArrayLike], class_ids) @@ -88,9 +96,10 @@ def test_mesh3d() -> None: print( f"E: rr.Mesh3D(\n" - f" vertex_normals={vertex_positions}\n" + f" vertex_positions={vertex_positions}\n" f" vertex_normals={vertex_normals}\n" f" vertex_colors={vertex_colors}\n" + f" vertex_texcoords={vertex_texcoords}\n" f" mesh_properties={mesh_properties_objects}\n" f" mesh_material={mesh_material}\n" f" class_ids={class_ids}\n" @@ -101,6 +110,7 @@ def test_mesh3d() -> None: vertex_positions=vertex_positions, vertex_normals=vertex_normals, vertex_colors=vertex_colors, + vertex_texcoords=vertex_texcoords, mesh_properties=mesh_properties, mesh_material=mesh_material, class_ids=class_ids, @@ -111,6 +121,7 @@ def test_mesh3d() -> None: assert arch.vertex_positions == vec3ds_expected(vertex_positions, Position3DBatch) assert arch.vertex_normals == vec3ds_expected(vertex_normals, Vector3DBatch) assert arch.vertex_colors == colors_expected(vertex_colors) + assert arch.vertex_texcoords == vec2ds_expected(vertex_texcoords, Texcoord2DBatch) assert arch.mesh_properties == mesh_properties_expected(mesh_properties) assert arch.mesh_material == mesh_material_expected(mesh_material) assert arch.class_ids == class_ids_expected(class_ids) @@ -124,11 +135,20 @@ def test_nullable_albedo_factor() -> None: MaterialBatch( [ Material(albedo_factor=[0xCC, 0x00, 0xCC, 0xFF]), + ] + ) + ) + == 1 + ) + assert ( + len( + MaterialBatch( + [ Material(), ] ) ) - == 2 + == 1 ) diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index 4d67b8c20316..47926806dfaa 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -55,6 +55,7 @@ disallowed-types = [ # Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ # You must also update the same list in the root `clippy.toml`! + "..", "GitHub", "GLB", "GLTF", @@ -62,11 +63,11 @@ doc-valid-idents = [ "macOS", "NaN", "OBJ", + "OpenGL", "PyPI", "sRGB", "sRGBA", "WebGL", "WebSocket", "WebSockets", - "..", ]