From cdeac625ab7cbbe46dcee8503b895313377939d0 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 31 Jul 2024 18:41:26 +0200 Subject: [PATCH] New `LeafTransform3D`, replacing `OutOfTreeTransform3D` (#7015) ### What * Part of #6831 * Replacement for #6988 Introduces a new `LeafTransform3D` archetype that is always applicable. It entails a copy of all of `Transform3D`'s components - axis length and transform relation have been omitted so far. Surprisingly, I didn't have much need for the extensive extensions we have on `Transform3D` so far: Leaf transform is much less commonly used and deals with arrays, making it sufficiently different from `Transform3D`. Also a lot of the extensions associated with `Transform3D` are there for legacy reasons - with the new more componetized interface we get much more reasonable ergonomics out of the box! This PR entails a major overhaul of the `TransformContext`. For *sure* not the last time we do this (looking at you 2D transform handling & not-so-great 2D<->3D interactions!), but the intention is to be a bit more forward looking and to enforce use of leaf transforms everywhere. Single component leaf transforms are supported everywhere now. Multi component leaf transforms logs a warning for all visualizers except Mesh3D and Asset3D where it bottoms out in instantiating the mesh multiple times: https://github.com/user-attachments/assets/62d26661-cd8c-4b4a-b912-063ef60e063a Snippet demonstrating combination of `Transform3D` with `LeafTransforms3D`: https://github.com/user-attachments/assets/ebb2ce5b-6d9a-407d-9f21-57b92f4ac25c Follow-up PRs will improve the interaction of various archetypes with `LeafTransforms3D` as well as remove now unused legacy types. ### Checklist * [x] run main ci to ensure that roundtrip & snippet tests are passing * [x] check transform checklist again! * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using examples from latest `main` build: [rerun.io/viewer](https://rerun.io/viewer/pr/7015?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [rerun.io/viewer](https://rerun.io/viewer/pr/7015?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG * [x] If applicable, add a new check to the [release checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)! * [x] If have noted any breaking changes to the log API in `CHANGELOG.md` and the migration guide - [PR Build Summary](https://build.rerun.io/pr/7015) - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html) To run all checks from `main`, comment on the PR with `@rerun-bot full-check`. --- Cargo.lock | 4 + crates/store/re_chunk/src/helpers.rs | 6 +- .../re_types/definitions/rerun/archetypes.fbs | 1 + .../definitions/rerun/archetypes/asset3d.fbs | 13 +- .../rerun/archetypes/leaf_transforms3d.fbs | 43 ++ .../definitions/rerun/archetypes/mesh3d.fbs | 4 + .../re_types/definitions/rerun/components.fbs | 1 - .../components/out_of_tree_transform3d.fbs | 13 - .../rerun/components/rotation_axis_angle.fbs | 9 + .../rerun/components/rotation_quat.fbs | 14 +- .../definitions/rerun/components/scale3d.fbs | 13 + .../rerun/components/transform_mat3x3.fbs | 43 ++ .../rerun/components/transform_relation.fbs | 4 +- .../rerun/components/translation3d.fbs | 9 + .../re_types/src/archetypes/.gitattributes | 1 + .../store/re_types/src/archetypes/asset3d.rs | 61 +- .../re_types/src/archetypes/asset3d_ext.rs | 1 - .../src/archetypes/leaf_transforms3d.rs | 386 +++++++++++ .../store/re_types/src/archetypes/mesh3d.rs | 60 +- crates/store/re_types/src/archetypes/mod.rs | 2 + .../re_types/src/components/.gitattributes | 6 +- ...sform3d.rs => leaf_rotation_axis_angle.rs} | 46 +- .../leaf_rotation_axis_angle_ext.rs | 19 + .../src/components/leaf_rotation_quat.rs | 116 ++++ .../src/components/leaf_rotation_quat_ext.rs | 14 + .../re_types/src/components/leaf_scale3d.rs | 117 ++++ .../src/components/leaf_scale3d_ext.rs | 36 + .../src/components/leaf_transform_mat3x3.rs | 125 ++++ .../components/leaf_transform_mat3x3_ext.rs | 15 + .../src/components/leaf_translation3d.rs | 113 ++++ .../src/components/leaf_translation3d_ext.rs | 15 + crates/store/re_types/src/components/mod.rs | 17 +- .../re_types/src/components/rotation_quat.rs | 2 +- .../src/components/transform_relation.rs | 8 +- crates/store/re_types/tests/asset3d.rs | 30 +- crates/top/rerun/src/sdk.rs | 4 +- .../re_data_ui/src/component_ui_registry.rs | 1 - crates/viewer/re_data_ui/src/transform3d.rs | 14 - .../viewer/re_space_view_spatial/Cargo.toml | 1 + .../re_space_view_spatial/src/contexts/mod.rs | 4 +- .../src/contexts/transform_context.rs | 639 +++++++++++++----- .../re_space_view_spatial/src/mesh_loader.rs | 6 +- .../src/transform_component_tracker.rs | 49 +- .../src/visualizers/arrows2d.rs | 15 +- .../src/visualizers/arrows3d.rs | 15 +- .../src/visualizers/assets3d.rs | 95 +-- .../src/visualizers/boxes2d.rs | 15 +- .../src/visualizers/boxes3d.rs | 15 +- .../src/visualizers/cameras.rs | 55 +- .../src/visualizers/depth_images.rs | 65 +- .../src/visualizers/ellipsoids.rs | 15 +- .../src/visualizers/image_encoded.rs | 21 +- .../src/visualizers/images.rs | 31 +- .../src/visualizers/lines2d.rs | 15 +- .../src/visualizers/lines3d.rs | 15 +- .../src/visualizers/meshes.rs | 41 +- .../src/visualizers/mod.rs | 11 +- .../src/visualizers/points2d.rs | 14 +- .../src/visualizers/points3d.rs | 15 +- .../src/visualizers/segmentation_images.rs | 22 +- .../src/visualizers/transform3d_arrows.rs | 24 +- .../visualizers/utilities/entity_iterator.rs | 46 +- .../src/visualizers/utilities/mod.rs | 4 +- .../visualizers/utilities/textured_rect.rs | 60 +- crates/viewer/re_viewer/src/reflection/mod.rs | 68 +- .../reference/migration/migration-0-18.md | 69 +- docs/content/reference/types/archetypes.md | 1 + .../reference/types/archetypes/.gitattributes | 1 + .../reference/types/archetypes/asset3d.md | 11 +- .../types/archetypes/leaf_transforms3d.md | 47 ++ .../reference/types/archetypes/mesh3d.md | 15 + docs/content/reference/types/components.md | 6 +- .../reference/types/components/.gitattributes | 6 +- .../components/leaf_rotation_axis_angle.md | 20 + .../types/components/leaf_rotation_quat.md | 23 + .../types/components/leaf_scale3d.md | 24 + .../types/components/leaf_transform_mat3x3.md | 32 + .../types/components/leaf_translation3d.md | 20 + .../components/out_of_tree_transform3d.md | 22 - .../types/components/rotation_quat.md | 2 +- .../reference/types/datatypes/mat3x3.md | 1 + .../reference/types/datatypes/quaternion.md | 1 + .../types/datatypes/rotation_axis_angle.md | 1 + .../reference/types/datatypes/transform3d.md | 1 - .../reference/types/datatypes/vec3d.md | 2 + .../reference/types/views/spatial2d_view.md | 1 + .../reference/types/views/spatial3d_view.md | 1 + .../all/archetypes/asset3d_out_of_tree.cpp | 39 -- .../all/archetypes/asset3d_out_of_tree.py | 31 - .../all/archetypes/asset3d_out_of_tree.rs | 40 -- .../archetypes/leaf_transforms3d_combined.cpp | 36 + .../archetypes/leaf_transforms3d_combined.py | 21 + .../archetypes/leaf_transforms3d_combined.rs | 45 ++ .../archetypes/mesh3d_leaf_transforms3d.cpp | 38 ++ .../archetypes/mesh3d_leaf_transforms3d.py | 30 + .../archetypes/mesh3d_leaf_transforms3d.rs | 44 ++ docs/snippets/snippets.toml | 11 + .../python/signed_distance_fields/README.md | 7 +- .../signed_distance_fields/__main__.py | 7 +- rerun_cpp/src/rerun.hpp | 1 - rerun_cpp/src/rerun/archetypes.hpp | 1 + rerun_cpp/src/rerun/archetypes/.gitattributes | 2 + rerun_cpp/src/rerun/archetypes/asset3d.cpp | 7 +- rerun_cpp/src/rerun/archetypes/asset3d.hpp | 18 +- .../rerun/archetypes/leaf_transforms3d.cpp | 53 ++ .../rerun/archetypes/leaf_transforms3d.hpp | 163 +++++ rerun_cpp/src/rerun/archetypes/mesh3d.hpp | 47 +- rerun_cpp/src/rerun/components.hpp | 6 +- rerun_cpp/src/rerun/components/.gitattributes | 6 +- .../components/leaf_rotation_axis_angle.hpp | 60 ++ .../rerun/components/leaf_rotation_quat.hpp | 60 ++ .../src/rerun/components/leaf_scale3d.hpp | 89 +++ .../src/rerun/components/leaf_scale3d_ext.cpp | 27 + .../components/leaf_transform_mat3x3.hpp | 74 ++ .../rerun/components/leaf_translation3d.hpp | 83 +++ .../components/leaf_translation3d_ext.cpp | 25 + .../components/out_of_tree_transform3d.hpp | 73 -- .../src/rerun/components/rotation_quat.hpp | 2 +- .../rerun/components/transform_relation.hpp | 4 +- rerun_py/docs/gen_common_index.py | 3 +- rerun_py/rerun_sdk/rerun/__init__.py | 3 +- .../rerun_sdk/rerun/archetypes/.gitattributes | 1 + .../rerun_sdk/rerun/archetypes/__init__.py | 2 + .../rerun_sdk/rerun/archetypes/asset3d.py | 15 +- .../rerun_sdk/rerun/archetypes/asset3d_ext.py | 8 +- .../rerun/archetypes/leaf_transforms3d.py | 177 +++++ rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py | 48 +- .../rerun_sdk/rerun/components/.gitattributes | 6 +- .../rerun_sdk/rerun/components/__init__.py | 24 +- .../components/leaf_rotation_axis_angle.py | 36 + .../rerun/components/leaf_rotation_quat.py | 41 ++ .../rerun/components/leaf_scale3d.py | 43 ++ .../rerun/components/leaf_scale3d_ext.py | 32 + .../rerun/components/leaf_transform_mat3x3.py | 72 ++ .../rerun/components/leaf_translation3d.py | 36 + .../components/out_of_tree_transform3d.py | 40 -- .../rerun/components/rotation_quat.py | 2 +- .../rerun/components/transform_relation.py | 4 +- rerun_py/tests/unit/test_asset3d.py | 13 - rerun_py/tests/unit/test_leaf_transforms3d.py | 85 +++ .../check_all_components_ui.py | 7 - 141 files changed, 3863 insertions(+), 1054 deletions(-) create mode 100644 crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs delete mode 100644 crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs create mode 100644 crates/store/re_types/src/archetypes/leaf_transforms3d.rs rename crates/store/re_types/src/components/{out_of_tree_transform3d.rs => leaf_rotation_axis_angle.rs} (58%) create mode 100644 crates/store/re_types/src/components/leaf_rotation_axis_angle_ext.rs create mode 100644 crates/store/re_types/src/components/leaf_rotation_quat.rs create mode 100644 crates/store/re_types/src/components/leaf_rotation_quat_ext.rs create mode 100644 crates/store/re_types/src/components/leaf_scale3d.rs create mode 100644 crates/store/re_types/src/components/leaf_scale3d_ext.rs create mode 100644 crates/store/re_types/src/components/leaf_transform_mat3x3.rs create mode 100644 crates/store/re_types/src/components/leaf_transform_mat3x3_ext.rs create mode 100644 crates/store/re_types/src/components/leaf_translation3d.rs create mode 100644 crates/store/re_types/src/components/leaf_translation3d_ext.rs create mode 100644 docs/content/reference/types/archetypes/leaf_transforms3d.md create mode 100644 docs/content/reference/types/components/leaf_rotation_axis_angle.md create mode 100644 docs/content/reference/types/components/leaf_rotation_quat.md create mode 100644 docs/content/reference/types/components/leaf_scale3d.md create mode 100644 docs/content/reference/types/components/leaf_transform_mat3x3.md create mode 100644 docs/content/reference/types/components/leaf_translation3d.md delete mode 100644 docs/content/reference/types/components/out_of_tree_transform3d.md delete mode 100644 docs/snippets/all/archetypes/asset3d_out_of_tree.cpp delete mode 100644 docs/snippets/all/archetypes/asset3d_out_of_tree.py delete mode 100644 docs/snippets/all/archetypes/asset3d_out_of_tree.rs create mode 100644 docs/snippets/all/archetypes/leaf_transforms3d_combined.cpp create mode 100644 docs/snippets/all/archetypes/leaf_transforms3d_combined.py create mode 100644 docs/snippets/all/archetypes/leaf_transforms3d_combined.rs create mode 100644 docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.cpp create mode 100644 docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.py create mode 100644 docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.rs create mode 100644 rerun_cpp/src/rerun/archetypes/leaf_transforms3d.cpp create mode 100644 rerun_cpp/src/rerun/archetypes/leaf_transforms3d.hpp create mode 100644 rerun_cpp/src/rerun/components/leaf_rotation_axis_angle.hpp create mode 100644 rerun_cpp/src/rerun/components/leaf_rotation_quat.hpp create mode 100644 rerun_cpp/src/rerun/components/leaf_scale3d.hpp create mode 100644 rerun_cpp/src/rerun/components/leaf_scale3d_ext.cpp create mode 100644 rerun_cpp/src/rerun/components/leaf_transform_mat3x3.hpp create mode 100644 rerun_cpp/src/rerun/components/leaf_translation3d.hpp create mode 100644 rerun_cpp/src/rerun/components/leaf_translation3d_ext.cpp delete mode 100644 rerun_cpp/src/rerun/components/out_of_tree_transform3d.hpp create mode 100644 rerun_py/rerun_sdk/rerun/archetypes/leaf_transforms3d.py create mode 100644 rerun_py/rerun_sdk/rerun/components/leaf_rotation_axis_angle.py create mode 100644 rerun_py/rerun_sdk/rerun/components/leaf_rotation_quat.py create mode 100644 rerun_py/rerun_sdk/rerun/components/leaf_scale3d.py create mode 100644 rerun_py/rerun_sdk/rerun/components/leaf_scale3d_ext.py create mode 100644 rerun_py/rerun_sdk/rerun/components/leaf_transform_mat3x3.py create mode 100644 rerun_py/rerun_sdk/rerun/components/leaf_translation3d.py delete mode 100644 rerun_py/rerun_sdk/rerun/components/out_of_tree_transform3d.py create mode 100644 rerun_py/tests/unit/test_leaf_transforms3d.py diff --git a/Cargo.lock b/Cargo.lock index 43c5eaf72990..874a1168eafe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5012,6 +5012,7 @@ dependencies = [ "re_viewport_blueprint", "serde", "smallvec", + "vec1", "web-time", ] @@ -6975,6 +6976,9 @@ name = "vec1" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bda7c41ca331fe9a1c278a9e7ee055f4be7f5eb1c2b72f079b4ff8b5fce9d5c" +dependencies = [ + "smallvec", +] [[package]] name = "version_check" diff --git a/crates/store/re_chunk/src/helpers.rs b/crates/store/re_chunk/src/helpers.rs index f9969587ae9c..d26b7fca88c8 100644 --- a/crates/store/re_chunk/src/helpers.rs +++ b/crates/store/re_chunk/src/helpers.rs @@ -235,11 +235,13 @@ impl UnitChunkShared { /// The maximum value amongst all components is what's returned. #[inline] pub fn num_instances(&self) -> u64 { + debug_assert!(self.num_rows() == 1); self.components .values() .map(|list_array| { - list_array.validity().map_or_else( - || list_array.len(), + let array = list_array.value(0); + array.validity().map_or_else( + || array.len(), |validity| validity.len() - validity.unset_bits(), ) }) diff --git a/crates/store/re_types/definitions/rerun/archetypes.fbs b/crates/store/re_types/definitions/rerun/archetypes.fbs index 07af568d23d5..eb5e6c105bb2 100644 --- a/crates/store/re_types/definitions/rerun/archetypes.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes.fbs @@ -13,6 +13,7 @@ include "./archetypes/disconnected_space.fbs"; include "./archetypes/ellipsoids.fbs"; include "./archetypes/image.fbs"; include "./archetypes/image_encoded.fbs"; +include "./archetypes/leaf_transforms3d.fbs"; include "./archetypes/line_strips2d.fbs"; include "./archetypes/line_strips3d.fbs"; include "./archetypes/mesh3d.fbs"; diff --git a/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs index 974c1b4ea43b..c2942a1bbee9 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/asset3d.fbs @@ -6,10 +6,12 @@ namespace rerun.archetypes; /// /// See also [archetypes.Mesh3D]. /// +/// If there are multiple [archetypes.LeafTransforms3D] instances logged to the same entity as a mesh, +/// an instance of the mesh will be drawn for each transform. +/// /// \example archetypes/asset3d_simple title="Simple 3D asset" image="https://static.rerun.io/asset3d_simple/af238578188d3fd0de3e330212120e2842a8ddb2/1200w.png" -/// \example archetypes/asset3d_out_of_tree !api title="3D asset with out-of-tree transform" table Asset3D ( - "attr.rust.derive": "PartialEq", + "attr.rust.derive": "PartialEq, Eq", "attr.docs.category": "Spatial 3D", "attr.docs.view_types": "Spatial3DView, Spatial2DView: if logged above active projection" ) { @@ -31,11 +33,4 @@ table Asset3D ( /// If omitted, the viewer will try to guess from the data blob. /// If it cannot guess, it won't be able to render the asset. media_type: rerun.components.MediaType ("attr.rerun.component_recommended", nullable, order: 2000); - - // --- Optional --- - - /// An out-of-tree transform. - /// - /// Applies a transformation to the asset itself without impacting its children. - transform: rerun.components.OutOfTreeTransform3D ("attr.rerun.component_optional", nullable, order: 3000); } diff --git a/crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs new file mode 100644 index 000000000000..eb07bb5da210 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs @@ -0,0 +1,43 @@ +namespace rerun.archetypes; + + +/// One or more transforms between the parent and the current entity which are *not* propagated in the transform hierarchy. +/// +/// For transforms that are propagated in the transform hierarchy, see [archetypes.Transform3D]. +/// +/// If both [archetypes.LeafTransforms3D] and [archetypes.Transform3D] are present, +/// first the tree propagating [archetypes.Transform3D] is applied, then [archetypes.LeafTransforms3D]. +/// +/// Currently, most visualizers support only a single leaf transform per entity. +/// Check archetype documentations for details - if not otherwise specified, only the first leaf transform is applied. +/// +/// From the point of view of the entity's coordinate system, +/// all components are applied in the inverse order they are listed here. +/// E.g. if both a translation and a max3x3 transform are present, +/// the 3x3 matrix is applied first, followed by the translation. +/// +/// \example archetypes/leaf_transforms3d_combined title="Regular & leaf transform in tandom" image="https://static.rerun.io/leaf_transform3d/41674f0082d6de489f8a1cd1583f60f6b5820ddf/1200w.png" +table LeafTransforms3D ( + "attr.rust.derive": "Default, PartialEq", + "attr.rust.generate_field_info", + "attr.docs.category": "Spatial 3D", + "attr.docs.view_types": "Spatial3DView, Spatial2DView: if logged above active projection" +) { + /// Translation vectors. + translations: [rerun.components.LeafTranslation3D] ("attr.rerun.component_optional", nullable, order: 1100); + + /// Rotations via axis + angle. + rotation_axis_angles: [rerun.components.LeafRotationAxisAngle] ("attr.rerun.component_optional", nullable, order: 1200); + + /// Rotations via quaternion. + quaternions: [rerun.components.LeafRotationQuat] ("attr.rerun.component_optional", nullable, order: 1300); + + /// Scaling factors. + scales: [rerun.components.LeafScale3D] ("attr.rerun.component_optional", nullable, order: 1400); + + /// 3x3 transformation matrices. + mat3x3: [rerun.components.LeafTransformMat3x3] ("attr.rerun.component_optional", nullable, order: 1500); + + // TODO(andreas): Support TransformRelation? + // TODO(andreas): Support axis_length? +} diff --git a/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs index c12a36f8d543..aa9fa48bd160 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs @@ -6,8 +6,12 @@ namespace rerun.archetypes; /// /// See also [archetypes.Asset3D]. /// +/// If there are multiple [archetypes.LeafTransforms3D] instances logged to the same entity as a mesh, +/// an instance of the mesh will be drawn for each transform. +/// /// \example archetypes/mesh3d_indexed title="Simple indexed 3D mesh" image="https://static.rerun.io/mesh3d_simple/e1e5fd97265daf0d0bc7b782d862f19086fd6975/1200w.png" /// \example archetypes/mesh3d_partial_updates !api title="3D mesh with partial updates" image="https://static.rerun.io/mesh3d_partial_updates/a11e4accb0257dcd9531867b7e1d6fd5e3bee5c3/1200w.png" +/// \example archetypes/mesh3d_leaf_transforms3d title="3D mesh with leaf transforms" image="https://static.rerun.io/mesh3d_leaf_transforms3d/c2d0ee033129da53168f5705625a9b033f3a3d61/1200w.png" table Mesh3D ( "attr.rust.derive": "PartialEq", "attr.docs.category": "Spatial 3D", diff --git a/crates/store/re_types/definitions/rerun/components.fbs b/crates/store/re_types/definitions/rerun/components.fbs index 06093a49364f..5140b0b39b4b 100644 --- a/crates/store/re_types/definitions/rerun/components.fbs +++ b/crates/store/re_types/definitions/rerun/components.fbs @@ -29,7 +29,6 @@ include "./components/marker_size.fbs"; include "./components/media_type.fbs"; include "./components/name.fbs"; include "./components/opacity.fbs"; -include "./components/out_of_tree_transform3d.fbs"; include "./components/pinhole_projection.fbs"; include "./components/position2d.fbs"; include "./components/position3d.fbs"; diff --git a/crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs b/crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs deleted file mode 100644 index 79c5a80f5508..000000000000 --- a/crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs +++ /dev/null @@ -1,13 +0,0 @@ -namespace rerun.components; - -// --- - -/// An out-of-tree affine transform between two 3D spaces, represented in a given direction. -/// -/// "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it. -table OutOfTreeTransform3D ( - "attr.rust.derive": "Default, PartialEq" -) { - /// Representation of the transform. - repr: rerun.datatypes.Transform3D (order: 100); -} diff --git a/crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs b/crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs index b807a746dd86..20c956535084 100644 --- a/crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs +++ b/crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs @@ -8,3 +8,12 @@ table RotationAxisAngle ( ) { rotation: rerun.datatypes.RotationAxisAngle (order: 100); } + +/// 3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy. +table LeafRotationAxisAngle ( + "attr.docs.unreleased", + "attr.rust.derive": "Default, Copy, PartialEq", + "attr.rust.repr": "transparent" +) { + rotation: rerun.datatypes.RotationAxisAngle (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/components/rotation_quat.fbs b/crates/store/re_types/definitions/rerun/components/rotation_quat.fbs index b88f69cce51f..b657f7db88e7 100644 --- a/crates/store/re_types/definitions/rerun/components/rotation_quat.fbs +++ b/crates/store/re_types/definitions/rerun/components/rotation_quat.fbs @@ -3,7 +3,7 @@ namespace rerun.components; /// A 3D rotation expressed as a quaternion. /// /// Note: although the x,y,z,w components of the quaternion will be passed through to the -/// datastore as provided, when used in the Viewer Quaternions will always be normalized. +/// datastore as provided, when used in the Viewer, quaternions will always be normalized. struct RotationQuat ( "attr.docs.unreleased", "attr.rust.derive": "Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", @@ -11,3 +11,15 @@ struct RotationQuat ( ) { quaternion: rerun.datatypes.Quaternion (order: 100); } + +/// A 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy. +/// +/// Note: although the x,y,z,w components of the quaternion will be passed through to the +/// datastore as provided, when used in the Viewer, quaternions will always be normalized. +struct LeafRotationQuat ( + "attr.docs.unreleased", + "attr.rust.derive": "Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent" +) { + quaternion: rerun.datatypes.Quaternion (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/components/scale3d.fbs b/crates/store/re_types/definitions/rerun/components/scale3d.fbs index 43acd0905fff..6ccf568ddb18 100644 --- a/crates/store/re_types/definitions/rerun/components/scale3d.fbs +++ b/crates/store/re_types/definitions/rerun/components/scale3d.fbs @@ -12,3 +12,16 @@ struct Scale3D ( ) { scale: rerun.datatypes.Vec3D (order: 100); } + +/// A 3D scale factor that doesn't propagate in the transform hierarchy. +/// +/// A scale of 1.0 means no scaling. +/// A scale of 2.0 means doubling the size. +/// Each component scales along the corresponding axis. +struct LeafScale3D ( + "attr.docs.unreleased", + "attr.rust.derive": "Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent" +) { + scale: rerun.datatypes.Vec3D (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/components/transform_mat3x3.fbs b/crates/store/re_types/definitions/rerun/components/transform_mat3x3.fbs index 9e502264c336..a8bc8c0a6194 100644 --- a/crates/store/re_types/definitions/rerun/components/transform_mat3x3.fbs +++ b/crates/store/re_types/definitions/rerun/components/transform_mat3x3.fbs @@ -43,3 +43,46 @@ struct TransformMat3x3 ( matrix: rerun.datatypes.Mat3x3 (order: 100); } + +/// A 3x3 transformation matrix Matrix that doesn't propagate in the transform hierarchy. +/// +/// 3x3 matrixes are able to represent any affine transformation in 3D space, +/// i.e. rotation, scaling, shearing, reflection etc. +/// +/// Matrices in Rerun are stored as flat list of coefficients in column-major order: +/// ```text +/// column 0 column 1 column 2 +/// ------------------------------------------------- +/// row 0 | flat_columns[0] flat_columns[3] flat_columns[6] +/// row 1 | flat_columns[1] flat_columns[4] flat_columns[7] +/// row 2 | flat_columns[2] flat_columns[5] flat_columns[8] +/// ``` +/// +/// \py However, construction is done from a list of rows, which follows NumPy's convention: +/// \py ```python +/// \py np.testing.assert_array_equal( +/// \py rr.components.LeafTransformMat3x3([1, 2, 3, 4, 5, 6, 7, 8, 9]).flat_columns, np.array([1, 4, 7, 2, 5, 8, 3, 6, 9], dtype=np.float32) +/// \py ) +/// \py np.testing.assert_array_equal( +/// \py rr.components.LeafTransformMat3x3([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).flat_columns, +/// \py np.array([1, 4, 7, 2, 5, 8, 3, 6, 9], dtype=np.float32), +/// \py ) +/// \py ``` +/// \py If you want to construct a matrix from a list of columns instead, use the named `columns` parameter: +/// \py ```python +/// \py np.testing.assert_array_equal( +/// \py rr.components.LeafTransformMat3x3(columns=[1, 2, 3, 4, 5, 6, 7, 8, 9]).flat_columns, +/// \py np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float32), +/// \py ) +/// \py np.testing.assert_array_equal( +/// \py rr.components.LeafTransformMat3x3(columns=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]).flat_columns, +/// \py np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float32), +/// \py ) +/// \py ``` +struct LeafTransformMat3x3 ( + "attr.docs.unreleased", + "attr.rust.derive": "Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent" +) { + matrix: rerun.datatypes.Mat3x3 (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/components/transform_relation.fbs b/crates/store/re_types/definitions/rerun/components/transform_relation.fbs index 94543ceb28ab..2deaef624dca 100644 --- a/crates/store/re_types/definitions/rerun/components/transform_relation.fbs +++ b/crates/store/re_types/definitions/rerun/components/transform_relation.fbs @@ -6,14 +6,14 @@ enum TransformRelation: byte ( ) { /// The transform describes how to transform into the parent entity's space. /// - /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + /// E.g. a translation of (0, 1, 0) with this [components.TransformRelation] logged at `parent/child` means /// that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis. /// From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis. ParentFromChild(default), /// The transform describes how to transform into the child entity's space. /// - /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + /// E.g. a translation of (0, 1, 0) with this [components.TransformRelation] logged at `parent/child` means /// that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis. /// From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis. ChildFromParent, diff --git a/crates/store/re_types/definitions/rerun/components/translation3d.fbs b/crates/store/re_types/definitions/rerun/components/translation3d.fbs index cd31895e01b7..61deaf3cbfa2 100644 --- a/crates/store/re_types/definitions/rerun/components/translation3d.fbs +++ b/crates/store/re_types/definitions/rerun/components/translation3d.fbs @@ -8,3 +8,12 @@ struct Translation3D ( ) { vector: rerun.datatypes.Vec3D (order: 100); } + +/// A translation vector in 3D space that doesn't propagate in the transform hierarchy. +struct LeafTranslation3D ( + "attr.docs.unreleased", + "attr.rust.derive": "Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent" +) { + vector: rerun.datatypes.Vec3D (order: 100); +} diff --git a/crates/store/re_types/src/archetypes/.gitattributes b/crates/store/re_types/src/archetypes/.gitattributes index a11fab28307e..e06018ef07f6 100644 --- a/crates/store/re_types/src/archetypes/.gitattributes +++ b/crates/store/re_types/src/archetypes/.gitattributes @@ -13,6 +13,7 @@ disconnected_space.rs linguist-generated=true ellipsoids.rs linguist-generated=true image.rs linguist-generated=true image_encoded.rs linguist-generated=true +leaf_transforms3d.rs linguist-generated=true line_strips2d.rs linguist-generated=true line_strips3d.rs linguist-generated=true mesh3d.rs linguist-generated=true diff --git a/crates/store/re_types/src/archetypes/asset3d.rs b/crates/store/re_types/src/archetypes/asset3d.rs index 1c34dd174190..da567d13824a 100644 --- a/crates/store/re_types/src/archetypes/asset3d.rs +++ b/crates/store/re_types/src/archetypes/asset3d.rs @@ -22,6 +22,9 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// See also [`archetypes::Mesh3D`][crate::archetypes::Mesh3D]. /// +/// If there are multiple [`archetypes::LeafTransforms3D`][crate::archetypes::LeafTransforms3D] instances logged to the same entity as a mesh, +/// an instance of the mesh will be drawn for each transform. +/// /// ## Example /// /// ### Simple 3D asset @@ -51,7 +54,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// /// -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Asset3D { /// The asset's bytes. pub blob: crate::components::Blob, @@ -67,26 +70,17 @@ pub struct Asset3D { /// If omitted, the viewer will try to guess from the data blob. /// If it cannot guess, it won't be able to render the asset. pub media_type: Option, - - /// An out-of-tree transform. - /// - /// Applies a transformation to the asset itself without impacting its children. - pub transform: Option, } impl ::re_types_core::SizeBytes for Asset3D { #[inline] fn heap_size_bytes(&self) -> u64 { - self.blob.heap_size_bytes() - + self.media_type.heap_size_bytes() - + self.transform.heap_size_bytes() + self.blob.heap_size_bytes() + self.media_type.heap_size_bytes() } #[inline] fn is_pod() -> bool { - ::is_pod() - && >::is_pod() - && >::is_pod() + ::is_pod() && >::is_pod() } } @@ -101,22 +95,21 @@ static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 2usize]> = ] }); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = - once_cell::sync::Lazy::new(|| ["rerun.components.OutOfTreeTransform3D".into()]); +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 0usize]> = + once_cell::sync::Lazy::new(|| []); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Blob".into(), "rerun.components.MediaType".into(), "rerun.components.Asset3DIndicator".into(), - "rerun.components.OutOfTreeTransform3D".into(), ] }); impl Asset3D { - /// The total number of components in the archetype: 1 required, 2 recommended, 1 optional - pub const NUM_COMPONENTS: usize = 4usize; + /// The total number of components in the archetype: 1 required, 2 recommended, 0 optional + pub const NUM_COMPONENTS: usize = 3usize; } /// Indicator component for the [`Asset3D`] [`::re_types_core::Archetype`] @@ -193,21 +186,7 @@ impl ::re_types_core::Archetype for Asset3D { } else { None }; - let transform = - if let Some(array) = arrays_by_name.get("rerun.components.OutOfTreeTransform3D") { - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Asset3D#transform")? - .into_iter() - .next() - .flatten() - } else { - None - }; - Ok(Self { - blob, - media_type, - transform, - }) + Ok(Self { blob, media_type }) } } @@ -221,9 +200,6 @@ impl ::re_types_core::AsComponents for Asset3D { self.media_type .as_ref() .map(|comp| (comp as &dyn ComponentBatch).into()), - self.transform - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch).into()), ] .into_iter() .flatten() @@ -238,7 +214,6 @@ impl Asset3D { Self { blob: blob.into(), media_type: None, - transform: None, } } @@ -257,16 +232,4 @@ impl Asset3D { self.media_type = Some(media_type.into()); self } - - /// An out-of-tree transform. - /// - /// Applies a transformation to the asset itself without impacting its children. - #[inline] - pub fn with_transform( - mut self, - transform: impl Into, - ) -> Self { - self.transform = Some(transform.into()); - self - } } diff --git a/crates/store/re_types/src/archetypes/asset3d_ext.rs b/crates/store/re_types/src/archetypes/asset3d_ext.rs index ad11edb9ba5b..78fd8a78f998 100644 --- a/crates/store/re_types/src/archetypes/asset3d_ext.rs +++ b/crates/store/re_types/src/archetypes/asset3d_ext.rs @@ -36,7 +36,6 @@ impl Asset3D { Self { blob: contents.into(), media_type, - transform: None, } } } diff --git a/crates/store/re_types/src/archetypes/leaf_transforms3d.rs b/crates/store/re_types/src/archetypes/leaf_transforms3d.rs new file mode 100644 index 000000000000..8802f630cd9e --- /dev/null +++ b/crates/store/re_types/src/archetypes/leaf_transforms3d.rs @@ -0,0 +1,386 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +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}; + +/// **Archetype**: One or more transforms between the parent and the current entity which are *not* propagated in the transform hierarchy. +/// +/// For transforms that are propagated in the transform hierarchy, see [`archetypes::Transform3D`][crate::archetypes::Transform3D]. +/// +/// If both [`archetypes::LeafTransforms3D`][crate::archetypes::LeafTransforms3D] and [`archetypes::Transform3D`][crate::archetypes::Transform3D] are present, +/// first the tree propagating [`archetypes::Transform3D`][crate::archetypes::Transform3D] is applied, then [`archetypes::LeafTransforms3D`][crate::archetypes::LeafTransforms3D]. +/// +/// Currently, most visualizers support only a single leaf transform per entity. +/// Check archetype documentations for details - if not otherwise specified, only the first leaf transform is applied. +/// +/// From the point of view of the entity's coordinate system, +/// all components are applied in the inverse order they are listed here. +/// E.g. if both a translation and a max3x3 transform are present, +/// the 3x3 matrix is applied first, followed by the translation. +/// +/// ## Example +/// +/// ### Regular & leaf transform in tandom +/// ```ignore +/// use rerun::{ +/// demo_util::grid, +/// external::{anyhow, glam}, +/// }; +/// +/// fn main() -> anyhow::Result<()> { +/// let rec = +/// rerun::RecordingStreamBuilder::new("rerun_example_leaf_transform3d_combined").spawn()?; +/// +/// rec.set_time_sequence("frame", 0); +/// +/// // Log a box and points further down in the hierarchy. +/// rec.log( +/// "world/box", +/// &rerun::Boxes3D::from_half_sizes([[1.0, 1.0, 1.0]]), +/// )?; +/// rec.log( +/// "world/box/points", +/// &rerun::Points3D::new(grid(glam::Vec3::splat(-10.0), glam::Vec3::splat(10.0), 10)), +/// )?; +/// +/// for i in 0..180 { +/// rec.set_time_sequence("frame", i); +/// +/// // Log a regular transform which affects both the box and the points. +/// rec.log( +/// "world/box", +/// &rerun::Transform3D::from_rotation(rerun::RotationAxisAngle { +/// axis: [0.0, 0.0, 1.0].into(), +/// angle: rerun::Angle::from_degrees(i as f32 * 2.0), +/// }), +/// )?; +/// +/// // Log an leaf transform which affects only the box. +/// let translation = [0.0, 0.0, (i as f32 * 0.1 - 5.0).abs() - 5.0]; +/// rec.log( +/// "world/box", +/// &rerun::LeafTransforms3D::new().with_translations([translation]), +/// )?; +/// } +/// +/// Ok(()) +/// } +/// ``` +///
+/// +/// +/// +/// +/// +/// +/// +///
+#[derive(Clone, Debug, Default, PartialEq)] +pub struct LeafTransforms3D { + /// Translation vectors. + pub translations: Option>, + + /// Rotations via axis + angle. + pub rotation_axis_angles: Option>, + + /// Rotations via quaternion. + pub quaternions: Option>, + + /// Scaling factors. + pub scales: Option>, + + /// 3x3 transformation matrices. + pub mat3x3: Option>, +} + +impl ::re_types_core::SizeBytes for LeafTransforms3D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.translations.heap_size_bytes() + + self.rotation_axis_angles.heap_size_bytes() + + self.quaternions.heap_size_bytes() + + self.scales.heap_size_bytes() + + self.mat3x3.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + >>::is_pod() + && >>::is_pod() + && >>::is_pod() + && >>::is_pod() + && >>::is_pod() + } +} + +static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 0usize]> = + once_cell::sync::Lazy::new(|| []); + +static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = + once_cell::sync::Lazy::new(|| ["rerun.components.LeafTransforms3DIndicator".into()]); + +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 5usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.components.LeafTranslation3D".into(), + "rerun.components.LeafRotationAxisAngle".into(), + "rerun.components.LeafRotationQuat".into(), + "rerun.components.LeafScale3D".into(), + "rerun.components.LeafTransformMat3x3".into(), + ] + }); + +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 6usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.components.LeafTransforms3DIndicator".into(), + "rerun.components.LeafTranslation3D".into(), + "rerun.components.LeafRotationAxisAngle".into(), + "rerun.components.LeafRotationQuat".into(), + "rerun.components.LeafScale3D".into(), + "rerun.components.LeafTransformMat3x3".into(), + ] + }); + +impl LeafTransforms3D { + /// The total number of components in the archetype: 0 required, 1 recommended, 5 optional + pub const NUM_COMPONENTS: usize = 6usize; +} + +/// Indicator component for the [`LeafTransforms3D`] [`::re_types_core::Archetype`] +pub type LeafTransforms3DIndicator = ::re_types_core::GenericIndicatorComponent; + +impl ::re_types_core::Archetype for LeafTransforms3D { + type Indicator = LeafTransforms3DIndicator; + + #[inline] + fn name() -> ::re_types_core::ArchetypeName { + "rerun.archetypes.LeafTransforms3D".into() + } + + #[inline] + fn display_name() -> &'static str { + "Leaf transforms 3D" + } + + #[inline] + fn indicator() -> MaybeOwnedComponentBatch<'static> { + static INDICATOR: LeafTransforms3DIndicator = LeafTransforms3DIndicator::DEFAULT; + MaybeOwnedComponentBatch::Ref(&INDICATOR) + } + + #[inline] + fn required_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + REQUIRED_COMPONENTS.as_slice().into() + } + + #[inline] + fn recommended_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + RECOMMENDED_COMPONENTS.as_slice().into() + } + + #[inline] + fn optional_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + OPTIONAL_COMPONENTS.as_slice().into() + } + + #[inline] + fn all_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + ALL_COMPONENTS.as_slice().into() + } + + #[inline] + fn from_arrow_components( + arrow_data: impl IntoIterator)>, + ) -> DeserializationResult { + re_tracing::profile_function!(); + use ::re_types_core::{Loggable as _, ResultExt as _}; + let arrays_by_name: ::std::collections::HashMap<_, _> = arrow_data + .into_iter() + .map(|(name, array)| (name.full_name(), array)) + .collect(); + let translations = + if let Some(array) = arrays_by_name.get("rerun.components.LeafTranslation3D") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.LeafTransforms3D#translations")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.LeafTransforms3D#translations")? + }) + } else { + None + }; + let rotation_axis_angles = + if let Some(array) = arrays_by_name.get("rerun.components.LeafRotationAxisAngle") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.LeafTransforms3D#rotation_axis_angles")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.LeafTransforms3D#rotation_axis_angles")? + }) + } else { + None + }; + let quaternions = + if let Some(array) = arrays_by_name.get("rerun.components.LeafRotationQuat") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.LeafTransforms3D#quaternions")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.LeafTransforms3D#quaternions")? + }) + } else { + None + }; + let scales = if let Some(array) = arrays_by_name.get("rerun.components.LeafScale3D") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.LeafTransforms3D#scales")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.LeafTransforms3D#scales")? + }) + } else { + None + }; + let mat3x3 = if let Some(array) = arrays_by_name.get("rerun.components.LeafTransformMat3x3") + { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.LeafTransforms3D#mat3x3")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.LeafTransforms3D#mat3x3")? + }) + } else { + None + }; + Ok(Self { + translations, + rotation_axis_angles, + quaternions, + scales, + mat3x3, + }) + } +} + +impl ::re_types_core::AsComponents for LeafTransforms3D { + fn as_component_batches(&self) -> Vec> { + re_tracing::profile_function!(); + use ::re_types_core::Archetype as _; + [ + Some(Self::indicator()), + self.translations + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.rotation_axis_angles + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.quaternions + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.scales + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.mat3x3 + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + ] + .into_iter() + .flatten() + .collect() + } +} + +impl ::re_types_core::ArchetypeReflectionMarker for LeafTransforms3D {} + +impl LeafTransforms3D { + /// Create a new `LeafTransforms3D`. + #[inline] + pub fn new() -> Self { + Self { + translations: None, + rotation_axis_angles: None, + quaternions: None, + scales: None, + mat3x3: None, + } + } + + /// Translation vectors. + #[inline] + pub fn with_translations( + mut self, + translations: impl IntoIterator>, + ) -> Self { + self.translations = Some(translations.into_iter().map(Into::into).collect()); + self + } + + /// Rotations via axis + angle. + #[inline] + pub fn with_rotation_axis_angles( + mut self, + rotation_axis_angles: impl IntoIterator< + Item = impl Into, + >, + ) -> Self { + self.rotation_axis_angles = + Some(rotation_axis_angles.into_iter().map(Into::into).collect()); + self + } + + /// Rotations via quaternion. + #[inline] + pub fn with_quaternions( + mut self, + quaternions: impl IntoIterator>, + ) -> Self { + self.quaternions = Some(quaternions.into_iter().map(Into::into).collect()); + self + } + + /// Scaling factors. + #[inline] + pub fn with_scales( + mut self, + scales: impl IntoIterator>, + ) -> Self { + self.scales = Some(scales.into_iter().map(Into::into).collect()); + self + } + + /// 3x3 transformation matrices. + #[inline] + pub fn with_mat3x3( + mut self, + mat3x3: impl IntoIterator>, + ) -> Self { + self.mat3x3 = Some(mat3x3.into_iter().map(Into::into).collect()); + self + } +} diff --git a/crates/store/re_types/src/archetypes/mesh3d.rs b/crates/store/re_types/src/archetypes/mesh3d.rs index 83ab561562d7..624c98957d3a 100644 --- a/crates/store/re_types/src/archetypes/mesh3d.rs +++ b/crates/store/re_types/src/archetypes/mesh3d.rs @@ -22,7 +22,10 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// See also [`archetypes::Asset3D`][crate::archetypes::Asset3D]. /// -/// ## Example +/// If there are multiple [`archetypes::LeafTransforms3D`][crate::archetypes::LeafTransforms3D] instances logged to the same entity as a mesh, +/// an instance of the mesh will be drawn for each transform. +/// +/// ## Examples /// /// ### Simple indexed 3D mesh /// ```ignore @@ -49,6 +52,61 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// /// +/// +/// ### 3D mesh with leaf transforms +/// ```ignore +/// fn main() -> Result<(), Box> { +/// let rec = +/// rerun::RecordingStreamBuilder::new("rerun_example_mesh3d_leaf_transforms3d").spawn()?; +/// +/// rec.set_time_sequence("frame", 0); +/// rec.log( +/// "shape", +/// &rerun::Mesh3D::new([ +/// [1.0, 1.0, 1.0], +/// [-1.0, -1.0, 1.0], +/// [-1.0, 1.0, -1.0], +/// [1.0, -1.0, -1.0], +/// ]) +/// .with_triangle_indices([[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]) +/// .with_vertex_colors([0xFF0000FF, 0x00FF00FF, 0x00000FFFF, 0xFFFF00FF]), +/// )?; +/// // This box will not be affected by its parent's leaf transforms! +/// rec.log( +/// "shape/box", +/// &rerun::Boxes3D::from_half_sizes([[5.0, 5.0, 5.0]]), +/// )?; +/// +/// for i in 0..100 { +/// rec.set_time_sequence("frame", i); +/// rec.log( +/// "shape", +/// &rerun::LeafTransforms3D::default() +/// .with_translations([ +/// [2.0, 0.0, 0.0], +/// [0.0, 2.0, 0.0], +/// [0.0, -2.0, 0.0], +/// [-2.0, 0.0, 0.0], +/// ]) +/// .with_rotation_axis_angles([rerun::RotationAxisAngle::new( +/// [0.0, 0.0, 1.0], +/// rerun::Angle::from_degrees(i as f32 * 2.0), +/// )]), +/// )?; +/// } +/// +/// Ok(()) +/// } +/// ``` +///
+/// +/// +/// +/// +/// +/// +/// +///
#[derive(Clone, Debug, PartialEq)] pub struct Mesh3D { /// The positions of each vertex. diff --git a/crates/store/re_types/src/archetypes/mod.rs b/crates/store/re_types/src/archetypes/mod.rs index dcada0933ea3..1daaf00c374d 100644 --- a/crates/store/re_types/src/archetypes/mod.rs +++ b/crates/store/re_types/src/archetypes/mod.rs @@ -21,6 +21,7 @@ mod image; mod image_encoded; mod image_encoded_ext; mod image_ext; +mod leaf_transforms3d; mod line_strips2d; mod line_strips3d; mod mesh3d; @@ -57,6 +58,7 @@ pub use self::disconnected_space::DisconnectedSpace; pub use self::ellipsoids::Ellipsoids; pub use self::image::Image; pub use self::image_encoded::ImageEncoded; +pub use self::leaf_transforms3d::LeafTransforms3D; pub use self::line_strips2d::LineStrips2D; pub use self::line_strips3d::LineStrips3D; pub use self::mesh3d::Mesh3D; diff --git a/crates/store/re_types/src/components/.gitattributes b/crates/store/re_types/src/components/.gitattributes index ab6662dfba01..7b321253b632 100644 --- a/crates/store/re_types/src/components/.gitattributes +++ b/crates/store/re_types/src/components/.gitattributes @@ -21,6 +21,11 @@ half_size2d.rs linguist-generated=true half_size3d.rs linguist-generated=true image_plane_distance.rs linguist-generated=true keypoint_id.rs linguist-generated=true +leaf_rotation_axis_angle.rs linguist-generated=true +leaf_rotation_quat.rs linguist-generated=true +leaf_scale3d.rs linguist-generated=true +leaf_transform_mat3x3.rs linguist-generated=true +leaf_translation3d.rs linguist-generated=true line_strip2d.rs linguist-generated=true line_strip3d.rs linguist-generated=true magnification_filter.rs linguist-generated=true @@ -30,7 +35,6 @@ media_type.rs linguist-generated=true mod.rs linguist-generated=true name.rs linguist-generated=true opacity.rs linguist-generated=true -out_of_tree_transform3d.rs linguist-generated=true pinhole_projection.rs linguist-generated=true position2d.rs linguist-generated=true position3d.rs linguist-generated=true diff --git a/crates/store/re_types/src/components/out_of_tree_transform3d.rs b/crates/store/re_types/src/components/leaf_rotation_axis_angle.rs similarity index 58% rename from crates/store/re_types/src/components/out_of_tree_transform3d.rs rename to crates/store/re_types/src/components/leaf_rotation_axis_angle.rs index ab1ca3b068cb..62ca19ca7490 100644 --- a/crates/store/re_types/src/components/out_of_tree_transform3d.rs +++ b/crates/store/re_types/src/components/leaf_rotation_axis_angle.rs @@ -1,5 +1,5 @@ // DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs -// Based on "crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs". +// Based on "crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs". #![allow(unused_imports)] #![allow(unused_parens)] @@ -18,16 +18,12 @@ use ::re_types_core::SerializationResult; use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch}; use ::re_types_core::{DeserializationError, DeserializationResult}; -/// **Component**: An out-of-tree affine transform between two 3D spaces, represented in a given direction. -/// -/// "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it. -#[derive(Clone, Debug, Default, PartialEq)] -pub struct OutOfTreeTransform3D( - /// Representation of the transform. - pub crate::datatypes::Transform3D, -); +/// **Component**: 3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy. +#[derive(Clone, Debug, Default, Copy, PartialEq)] +#[repr(transparent)] +pub struct LeafRotationAxisAngle(pub crate::datatypes::RotationAxisAngle); -impl ::re_types_core::SizeBytes for OutOfTreeTransform3D { +impl ::re_types_core::SizeBytes for LeafRotationAxisAngle { #[inline] fn heap_size_bytes(&self) -> u64 { self.0.heap_size_bytes() @@ -35,52 +31,52 @@ impl ::re_types_core::SizeBytes for OutOfTreeTransform3D { #[inline] fn is_pod() -> bool { - ::is_pod() + ::is_pod() } } -impl> From for OutOfTreeTransform3D { +impl> From for LeafRotationAxisAngle { fn from(v: T) -> Self { Self(v.into()) } } -impl std::borrow::Borrow for OutOfTreeTransform3D { +impl std::borrow::Borrow for LeafRotationAxisAngle { #[inline] - fn borrow(&self) -> &crate::datatypes::Transform3D { + fn borrow(&self) -> &crate::datatypes::RotationAxisAngle { &self.0 } } -impl std::ops::Deref for OutOfTreeTransform3D { - type Target = crate::datatypes::Transform3D; +impl std::ops::Deref for LeafRotationAxisAngle { + type Target = crate::datatypes::RotationAxisAngle; #[inline] - fn deref(&self) -> &crate::datatypes::Transform3D { + fn deref(&self) -> &crate::datatypes::RotationAxisAngle { &self.0 } } -impl std::ops::DerefMut for OutOfTreeTransform3D { +impl std::ops::DerefMut for LeafRotationAxisAngle { #[inline] - fn deref_mut(&mut self) -> &mut crate::datatypes::Transform3D { + fn deref_mut(&mut self) -> &mut crate::datatypes::RotationAxisAngle { &mut self.0 } } -::re_types_core::macros::impl_into_cow!(OutOfTreeTransform3D); +::re_types_core::macros::impl_into_cow!(LeafRotationAxisAngle); -impl ::re_types_core::Loggable for OutOfTreeTransform3D { +impl ::re_types_core::Loggable for LeafRotationAxisAngle { type Name = ::re_types_core::ComponentName; #[inline] fn name() -> Self::Name { - "rerun.components.OutOfTreeTransform3D".into() + "rerun.components.LeafRotationAxisAngle".into() } #[inline] fn arrow_datatype() -> arrow2::datatypes::DataType { - crate::datatypes::Transform3D::arrow_datatype() + crate::datatypes::RotationAxisAngle::arrow_datatype() } fn to_arrow_opt<'a>( @@ -89,7 +85,7 @@ impl ::re_types_core::Loggable for OutOfTreeTransform3D { where Self: Clone + 'a, { - crate::datatypes::Transform3D::to_arrow_opt(data.into_iter().map(|datum| { + crate::datatypes::RotationAxisAngle::to_arrow_opt(data.into_iter().map(|datum| { datum.map(|datum| match datum.into() { ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), @@ -103,7 +99,7 @@ impl ::re_types_core::Loggable for OutOfTreeTransform3D { where Self: Sized, { - crate::datatypes::Transform3D::from_arrow_opt(arrow_data) + crate::datatypes::RotationAxisAngle::from_arrow_opt(arrow_data) .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) } } diff --git a/crates/store/re_types/src/components/leaf_rotation_axis_angle_ext.rs b/crates/store/re_types/src/components/leaf_rotation_axis_angle_ext.rs new file mode 100644 index 000000000000..02b86240949d --- /dev/null +++ b/crates/store/re_types/src/components/leaf_rotation_axis_angle_ext.rs @@ -0,0 +1,19 @@ +use crate::datatypes; + +use super::LeafRotationAxisAngle; + +impl LeafRotationAxisAngle { + /// Create a new rotation from an axis and an angle. + #[inline] + pub fn new(axis: impl Into, angle: impl Into) -> Self { + Self(datatypes::RotationAxisAngle::new(axis, angle)) + } +} + +#[cfg(feature = "glam")] +impl From for glam::Affine3A { + #[inline] + fn from(val: LeafRotationAxisAngle) -> Self { + Self::from_axis_angle(val.0.axis.into(), val.0.angle.radians()) + } +} diff --git a/crates/store/re_types/src/components/leaf_rotation_quat.rs b/crates/store/re_types/src/components/leaf_rotation_quat.rs new file mode 100644 index 000000000000..e063765e73f7 --- /dev/null +++ b/crates/store/re_types/src/components/leaf_rotation_quat.rs @@ -0,0 +1,116 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/components/rotation_quat.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +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 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy. +/// +/// Note: although the x,y,z,w components of the quaternion will be passed through to the +/// datastore as provided, when used in the Viewer, quaternions will always be normalized. +#[derive(Clone, Debug, Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct LeafRotationQuat(pub crate::datatypes::Quaternion); + +impl ::re_types_core::SizeBytes for LeafRotationQuat { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for LeafRotationQuat { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for LeafRotationQuat { + #[inline] + fn borrow(&self) -> &crate::datatypes::Quaternion { + &self.0 + } +} + +impl std::ops::Deref for LeafRotationQuat { + type Target = crate::datatypes::Quaternion; + + #[inline] + fn deref(&self) -> &crate::datatypes::Quaternion { + &self.0 + } +} + +impl std::ops::DerefMut for LeafRotationQuat { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Quaternion { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(LeafRotationQuat); + +impl ::re_types_core::Loggable for LeafRotationQuat { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.LeafRotationQuat".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + crate::datatypes::Quaternion::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + crate::datatypes::Quaternion::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Quaternion::from_arrow_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Quaternion::from_arrow(arrow_data).map(bytemuck::cast_vec) + } +} diff --git a/crates/store/re_types/src/components/leaf_rotation_quat_ext.rs b/crates/store/re_types/src/components/leaf_rotation_quat_ext.rs new file mode 100644 index 000000000000..769f4753df9b --- /dev/null +++ b/crates/store/re_types/src/components/leaf_rotation_quat_ext.rs @@ -0,0 +1,14 @@ +use super::LeafRotationQuat; + +impl LeafRotationQuat { + /// The identity rotation, representing no rotation. + pub const IDENTITY: Self = Self(crate::datatypes::Quaternion::IDENTITY); +} + +#[cfg(feature = "glam")] +impl From for glam::Affine3A { + #[inline] + fn from(val: LeafRotationQuat) -> Self { + Self::from_quat(val.0.into()) + } +} diff --git a/crates/store/re_types/src/components/leaf_scale3d.rs b/crates/store/re_types/src/components/leaf_scale3d.rs new file mode 100644 index 000000000000..c27856446135 --- /dev/null +++ b/crates/store/re_types/src/components/leaf_scale3d.rs @@ -0,0 +1,117 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/components/scale3d.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +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 3D scale factor that doesn't propagate in the transform hierarchy. +/// +/// A scale of 1.0 means no scaling. +/// A scale of 2.0 means doubling the size. +/// Each component scales along the corresponding axis. +#[derive(Clone, Debug, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct LeafScale3D(pub crate::datatypes::Vec3D); + +impl ::re_types_core::SizeBytes for LeafScale3D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for LeafScale3D { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for LeafScale3D { + #[inline] + fn borrow(&self) -> &crate::datatypes::Vec3D { + &self.0 + } +} + +impl std::ops::Deref for LeafScale3D { + type Target = crate::datatypes::Vec3D; + + #[inline] + fn deref(&self) -> &crate::datatypes::Vec3D { + &self.0 + } +} + +impl std::ops::DerefMut for LeafScale3D { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Vec3D { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(LeafScale3D); + +impl ::re_types_core::Loggable for LeafScale3D { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.LeafScale3D".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + crate::datatypes::Vec3D::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + crate::datatypes::Vec3D::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Vec3D::from_arrow_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Vec3D::from_arrow(arrow_data).map(bytemuck::cast_vec) + } +} diff --git a/crates/store/re_types/src/components/leaf_scale3d_ext.rs b/crates/store/re_types/src/components/leaf_scale3d_ext.rs new file mode 100644 index 000000000000..55a82498495f --- /dev/null +++ b/crates/store/re_types/src/components/leaf_scale3d_ext.rs @@ -0,0 +1,36 @@ +use crate::datatypes::Vec3D; + +use super::LeafScale3D; + +impl LeafScale3D { + /// Scale the same amount along all axis. + #[inline] + pub fn uniform(value: f32) -> Self { + Self(Vec3D([value, value, value])) + } +} + +impl From for LeafScale3D { + #[inline] + fn from(value: f32) -> Self { + Self(crate::datatypes::Vec3D([value, value, value])) + } +} + +#[cfg(feature = "glam")] +impl From for glam::Affine3A { + #[inline] + fn from(v: LeafScale3D) -> Self { + Self { + matrix3: glam::Mat3A::from_diagonal(v.0.into()), + translation: glam::Vec3A::ZERO, + } + } +} + +impl Default for LeafScale3D { + #[inline] + fn default() -> Self { + Self(crate::datatypes::Vec3D([1.0, 1.0, 1.0])) + } +} diff --git a/crates/store/re_types/src/components/leaf_transform_mat3x3.rs b/crates/store/re_types/src/components/leaf_transform_mat3x3.rs new file mode 100644 index 000000000000..322d91ca8c8f --- /dev/null +++ b/crates/store/re_types/src/components/leaf_transform_mat3x3.rs @@ -0,0 +1,125 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/components/transform_mat3x3.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +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 3x3 transformation matrix Matrix that doesn't propagate in the transform hierarchy. +/// +/// 3x3 matrixes are able to represent any affine transformation in 3D space, +/// i.e. rotation, scaling, shearing, reflection etc. +/// +/// Matrices in Rerun are stored as flat list of coefficients in column-major order: +/// ```text +/// column 0 column 1 column 2 +/// ------------------------------------------------- +/// row 0 | flat_columns[0] flat_columns[3] flat_columns[6] +/// row 1 | flat_columns[1] flat_columns[4] flat_columns[7] +/// row 2 | flat_columns[2] flat_columns[5] flat_columns[8] +/// ``` +#[derive(Clone, Debug, Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct LeafTransformMat3x3(pub crate::datatypes::Mat3x3); + +impl ::re_types_core::SizeBytes for LeafTransformMat3x3 { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for LeafTransformMat3x3 { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for LeafTransformMat3x3 { + #[inline] + fn borrow(&self) -> &crate::datatypes::Mat3x3 { + &self.0 + } +} + +impl std::ops::Deref for LeafTransformMat3x3 { + type Target = crate::datatypes::Mat3x3; + + #[inline] + fn deref(&self) -> &crate::datatypes::Mat3x3 { + &self.0 + } +} + +impl std::ops::DerefMut for LeafTransformMat3x3 { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Mat3x3 { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(LeafTransformMat3x3); + +impl ::re_types_core::Loggable for LeafTransformMat3x3 { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.LeafTransformMat3x3".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + crate::datatypes::Mat3x3::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + crate::datatypes::Mat3x3::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Mat3x3::from_arrow_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Mat3x3::from_arrow(arrow_data).map(bytemuck::cast_vec) + } +} diff --git a/crates/store/re_types/src/components/leaf_transform_mat3x3_ext.rs b/crates/store/re_types/src/components/leaf_transform_mat3x3_ext.rs new file mode 100644 index 000000000000..73f81b81e6ea --- /dev/null +++ b/crates/store/re_types/src/components/leaf_transform_mat3x3_ext.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "glam")] +use super::LeafTransformMat3x3; + +// This is intentionally not implemented for `Mat3x3`: +// The transform semantic is expressed here, `Mat3x3` on the other hand implements conversion to `glam::Mat3A`. +#[cfg(feature = "glam")] +impl From for glam::Affine3A { + #[inline] + fn from(v: LeafTransformMat3x3) -> Self { + Self { + matrix3: glam::Mat3A::from_cols_slice(&v.0 .0), + translation: glam::Vec3A::ZERO, + } + } +} diff --git a/crates/store/re_types/src/components/leaf_translation3d.rs b/crates/store/re_types/src/components/leaf_translation3d.rs new file mode 100644 index 000000000000..928b4470c762 --- /dev/null +++ b/crates/store/re_types/src/components/leaf_translation3d.rs @@ -0,0 +1,113 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/components/translation3d.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +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 translation vector in 3D space that doesn't propagate in the transform hierarchy. +#[derive(Clone, Debug, Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct LeafTranslation3D(pub crate::datatypes::Vec3D); + +impl ::re_types_core::SizeBytes for LeafTranslation3D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for LeafTranslation3D { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for LeafTranslation3D { + #[inline] + fn borrow(&self) -> &crate::datatypes::Vec3D { + &self.0 + } +} + +impl std::ops::Deref for LeafTranslation3D { + type Target = crate::datatypes::Vec3D; + + #[inline] + fn deref(&self) -> &crate::datatypes::Vec3D { + &self.0 + } +} + +impl std::ops::DerefMut for LeafTranslation3D { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Vec3D { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(LeafTranslation3D); + +impl ::re_types_core::Loggable for LeafTranslation3D { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.LeafTranslation3D".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + crate::datatypes::Vec3D::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + crate::datatypes::Vec3D::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Vec3D::from_arrow_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Vec3D::from_arrow(arrow_data).map(bytemuck::cast_vec) + } +} diff --git a/crates/store/re_types/src/components/leaf_translation3d_ext.rs b/crates/store/re_types/src/components/leaf_translation3d_ext.rs new file mode 100644 index 000000000000..9faa18ee4719 --- /dev/null +++ b/crates/store/re_types/src/components/leaf_translation3d_ext.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "glam")] +use super::LeafTranslation3D; + +// This is intentionally not implemented for `Vec3`: +// The transform semantic is expressed here, `Vec3` on the other hand implements conversion to `glam::Vec3A`. +#[cfg(feature = "glam")] +impl From for glam::Affine3A { + #[inline] + fn from(v: LeafTranslation3D) -> Self { + Self { + matrix3: glam::Mat3A::IDENTITY, + translation: glam::Vec3A::from_slice(&v.0 .0), + } + } +} diff --git a/crates/store/re_types/src/components/mod.rs b/crates/store/re_types/src/components/mod.rs index d52e2550b0f0..47b442724b8c 100644 --- a/crates/store/re_types/src/components/mod.rs +++ b/crates/store/re_types/src/components/mod.rs @@ -35,6 +35,16 @@ mod image_plane_distance; mod image_plane_distance_ext; mod keypoint_id; mod keypoint_id_ext; +mod leaf_rotation_axis_angle; +mod leaf_rotation_axis_angle_ext; +mod leaf_rotation_quat; +mod leaf_rotation_quat_ext; +mod leaf_scale3d; +mod leaf_scale3d_ext; +mod leaf_transform_mat3x3; +mod leaf_transform_mat3x3_ext; +mod leaf_translation3d; +mod leaf_translation3d_ext; mod line_strip2d; mod line_strip2d_ext; mod line_strip3d; @@ -50,7 +60,6 @@ mod name; mod name_ext; mod opacity; mod opacity_ext; -mod out_of_tree_transform3d; mod pinhole_projection; mod pinhole_projection_ext; mod position2d; @@ -124,6 +133,11 @@ pub use self::half_size2d::HalfSize2D; pub use self::half_size3d::HalfSize3D; pub use self::image_plane_distance::ImagePlaneDistance; pub use self::keypoint_id::KeypointId; +pub use self::leaf_rotation_axis_angle::LeafRotationAxisAngle; +pub use self::leaf_rotation_quat::LeafRotationQuat; +pub use self::leaf_scale3d::LeafScale3D; +pub use self::leaf_transform_mat3x3::LeafTransformMat3x3; +pub use self::leaf_translation3d::LeafTranslation3D; pub use self::line_strip2d::LineStrip2D; pub use self::line_strip3d::LineStrip3D; pub use self::magnification_filter::MagnificationFilter; @@ -132,7 +146,6 @@ pub use self::marker_size::MarkerSize; pub use self::media_type::MediaType; pub use self::name::Name; pub use self::opacity::Opacity; -pub use self::out_of_tree_transform3d::OutOfTreeTransform3D; pub use self::pinhole_projection::PinholeProjection; pub use self::position2d::Position2D; pub use self::position3d::Position3D; diff --git a/crates/store/re_types/src/components/rotation_quat.rs b/crates/store/re_types/src/components/rotation_quat.rs index 8f9629497b8e..1de2470e79ad 100644 --- a/crates/store/re_types/src/components/rotation_quat.rs +++ b/crates/store/re_types/src/components/rotation_quat.rs @@ -21,7 +21,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// **Component**: A 3D rotation expressed as a quaternion. /// /// Note: although the x,y,z,w components of the quaternion will be passed through to the -/// datastore as provided, when used in the Viewer Quaternions will always be normalized. +/// datastore as provided, when used in the Viewer, quaternions will always be normalized. #[derive(Clone, Debug, Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] #[repr(transparent)] pub struct RotationQuat(pub crate::datatypes::Quaternion); diff --git a/crates/store/re_types/src/components/transform_relation.rs b/crates/store/re_types/src/components/transform_relation.rs index 5af660a8ecc0..aa14638e7873 100644 --- a/crates/store/re_types/src/components/transform_relation.rs +++ b/crates/store/re_types/src/components/transform_relation.rs @@ -23,7 +23,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; pub enum TransformRelation { /// The transform describes how to transform into the parent entity's space. /// - /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + /// E.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means /// that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis. /// From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis. #[default] @@ -31,7 +31,7 @@ pub enum TransformRelation { /// The transform describes how to transform into the child entity's space. /// - /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + /// E.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means /// that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis. /// From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis. ChildFromParent = 2, @@ -47,10 +47,10 @@ impl ::re_types_core::reflection::Enum for TransformRelation { fn docstring_md(self) -> &'static str { match self { Self::ParentFromChild => { - "The transform describes how to transform into the parent entity's space.\n\nE.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis." + "The transform describes how to transform into the parent entity's space.\n\nE.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis." } Self::ChildFromParent => { - "The transform describes how to transform into the child entity's space.\n\nE.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis." + "The transform describes how to transform into the child entity's space.\n\nE.g. a translation of (0, 1, 0) with this [`components::TransformRelation`][crate::components::TransformRelation] logged at `parent/child` means\nthat from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis.\nFrom perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis." } } } diff --git a/crates/store/re_types/tests/asset3d.rs b/crates/store/re_types/tests/asset3d.rs index 113b6c6e34f0..0b697446340c 100644 --- a/crates/store/re_types/tests/asset3d.rs +++ b/crates/store/re_types/tests/asset3d.rs @@ -1,11 +1,7 @@ -use std::f32::consts::TAU; - use re_types::{ archetypes::Asset3D, - components::{Blob, MediaType, OutOfTreeTransform3D}, - datatypes::{ - Angle, Rotation3D, RotationAxisAngle, Scale3D, TranslationRotationScale3D, Utf8, Vec3D, - }, + components::{Blob, MediaType}, + datatypes::Utf8, Archetype as _, AsComponents as _, }; @@ -16,29 +12,9 @@ fn roundtrip() { let expected = Asset3D { blob: Blob(BYTES.to_vec().into()), media_type: Some(MediaType(Utf8(MediaType::GLTF.into()))), - transform: Some(OutOfTreeTransform3D( - re_types::datatypes::Transform3D::TranslationRotationScale( - TranslationRotationScale3D { - translation: Some(Vec3D([1.0, 2.0, 3.0])), - rotation: Some(Rotation3D::AxisAngle(RotationAxisAngle { - axis: Vec3D([0.2, 0.2, 0.8]), - angle: Angle::from_radians(0.5 * TAU), - })), - scale: Some(Scale3D::Uniform(42.0)), - from_parent: true, - }, - ), - )), // }; - let arch = Asset3D::from_file_contents(BYTES.to_vec(), Some(MediaType::gltf())).with_transform( - re_types::datatypes::Transform3D::from_translation_rotation_scale( - [1.0, 2.0, 3.0], - RotationAxisAngle::new([0.2, 0.2, 0.8], Angle::from_radians(0.5 * TAU)), - 42.0, - ) - .from_parent(), - ); + let arch = Asset3D::from_file_contents(BYTES.to_vec(), Some(MediaType::gltf())); similar_asserts::assert_eq!(expected, arch); // let expected_extensions: HashMap<_, _> = [ diff --git a/crates/top/rerun/src/sdk.rs b/crates/top/rerun/src/sdk.rs index 8c91db5388a0..fa335708472f 100644 --- a/crates/top/rerun/src/sdk.rs +++ b/crates/top/rerun/src/sdk.rs @@ -27,8 +27,8 @@ mod prelude { pub use re_chunk::ChunkTimeline; pub use re_types::components::{ AlbedoFactor, Color, FillMode, HalfSize2D, HalfSize3D, LineStrip2D, LineStrip3D, MediaType, - OutOfTreeTransform3D, Position2D, Position3D, Radius, Scale3D, Text, TextLogLevel, - TransformRelation, TriangleIndices, Vector2D, Vector3D, + Position2D, Position3D, Radius, Scale3D, Text, TextLogLevel, TransformRelation, + TriangleIndices, Vector2D, Vector3D, }; pub use re_types::datatypes::{ Angle, AnnotationInfo, ClassDescription, Float32, KeypointPair, Mat3x3, Quaternion, Rgba32, diff --git a/crates/viewer/re_data_ui/src/component_ui_registry.rs b/crates/viewer/re_data_ui/src/component_ui_registry.rs index c3bb679819e1..79a185606d3d 100644 --- a/crates/viewer/re_data_ui/src/component_ui_registry.rs +++ b/crates/viewer/re_data_ui/src/component_ui_registry.rs @@ -22,7 +22,6 @@ pub fn create_component_ui_registry() -> ComponentUiRegistry { add_to_registry::(&mut registry); add_to_registry::(&mut registry); add_to_registry::(&mut registry); - add_to_registry::(&mut registry); add_to_registry::(&mut registry); add_to_registry::(&mut registry); diff --git a/crates/viewer/re_data_ui/src/transform3d.rs b/crates/viewer/re_data_ui/src/transform3d.rs index ac62199d3097..65e89ca80d4a 100644 --- a/crates/viewer/re_data_ui/src/transform3d.rs +++ b/crates/viewer/re_data_ui/src/transform3d.rs @@ -45,20 +45,6 @@ impl DataUi for re_types::components::Transform3D { } } -impl DataUi for re_types::components::OutOfTreeTransform3D { - #[inline] - fn data_ui( - &self, - ctx: &ViewerContext<'_>, - ui: &mut egui::Ui, - ui_layout: UiLayout, - query: &re_chunk_store::LatestAtQuery, - db: &re_entity_db::EntityDb, - ) { - re_types::components::Transform3D(self.0).data_ui(ctx, ui, ui_layout, query, db); - } -} - impl DataUi for Transform3D { #[allow(clippy::only_used_in_recursion)] fn data_ui( diff --git a/crates/viewer/re_space_view_spatial/Cargo.toml b/crates/viewer/re_space_view_spatial/Cargo.toml index d538505fde6b..8998fc77afbc 100644 --- a/crates/viewer/re_space_view_spatial/Cargo.toml +++ b/crates/viewer/re_space_view_spatial/Cargo.toml @@ -53,6 +53,7 @@ nohash-hasher.workspace = true once_cell.workspace = true serde.workspace = true smallvec = { workspace = true, features = ["serde"] } +vec1 = { workspace = true, features = ["smallvec-v1"] } web-time.workspace = true diff --git a/crates/viewer/re_space_view_spatial/src/contexts/mod.rs b/crates/viewer/re_space_view_spatial/src/contexts/mod.rs index 709b93c2dd53..d7a425f20ccc 100644 --- a/crates/viewer/re_space_view_spatial/src/contexts/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/contexts/mod.rs @@ -5,7 +5,7 @@ mod transform_context; pub use annotation_context::AnnotationSceneContext; pub use depth_offsets::EntityDepthOffsets; use re_types::SpaceViewClassIdentifier; -pub use transform_context::TransformContext; +pub use transform_context::{TransformContext, TransformInfo, TwoDInThreeDTransformInfo}; // ----------------------------------------------------------------------------- @@ -14,7 +14,7 @@ use re_viewer_context::{Annotations, SpaceViewClassRegistryError}; /// Context objects for a single entity in a spatial scene. pub struct SpatialSceneEntityContext<'a> { - pub world_from_entity: glam::Affine3A, + pub transform_info: &'a TransformInfo, pub depth_offset: DepthOffset, pub annotations: std::sync::Arc, diff --git a/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs b/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs index f196f4e36c69..f202a828d7c6 100644 --- a/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs +++ b/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs @@ -1,3 +1,4 @@ +use itertools::Either; use nohash_hasher::IntMap; use re_chunk_store::LatestAtQuery; @@ -6,25 +7,95 @@ use re_space_view::DataResultQuery as _; use re_types::{ archetypes::Pinhole, components::{ - DisconnectedSpace, ImagePlaneDistance, PinholeProjection, RotationAxisAngle, RotationQuat, - Scale3D, Transform3D, TransformMat3x3, TransformRelation, Translation3D, ViewCoordinates, + DisconnectedSpace, ImagePlaneDistance, LeafRotationAxisAngle, LeafRotationQuat, + LeafScale3D, LeafTransformMat3x3, LeafTranslation3D, PinholeProjection, RotationAxisAngle, + RotationQuat, Scale3D, Transform3D, TransformMat3x3, TransformRelation, Translation3D, + ViewCoordinates, }, ComponentNameSet, Loggable as _, }; use re_viewer_context::{IdentifiedViewSystem, ViewContext, ViewContextSystem}; +use vec1::smallvec_v1::SmallVec1; -use crate::visualizers::image_view_coordinates; +use crate::{ + transform_component_tracker::TransformComponentTracker, visualizers::image_view_coordinates, +}; -#[derive(Clone)] -struct TransformInfo { +#[derive(Clone, Debug)] +pub struct TransformInfo { /// The transform from the entity to the reference space. - pub reference_from_entity: glam::Affine3A, + /// + /// ⚠️ Does not include per instance leaf transforms! ⚠️ + /// Include 3D-from-2D / 2D-from-3D pinhole transform if present. + reference_from_entity: glam::Affine3A, - /// The pinhole camera ancestor of this entity if any. + /// List of transforms per instance including leaf transforms. + /// + /// If no leaf transforms are present, this is always the same as `reference_from_entity`. + /// (also implying that in this case there is only a single element). + /// If there are leaf transforms there may be more than one element. + pub reference_from_instances: SmallVec1<[glam::Affine3A; 1]>, + + /// If this entity is under (!) a pinhole camera, this contains additional information. + /// + /// TODO(#2663, #1025): Going forward we should have separate transform hierarchies for 2D (i.e. projected) and 3D, + /// which would remove the need for this. + pub twod_in_threed_info: Option, +} + +#[derive(Clone, Debug)] +pub struct TwoDInThreeDTransformInfo { + /// Pinhole camera ancestor (may be this entity itself). /// /// None indicates that this entity is under the eye camera with no Pinhole camera in-between. /// Some indicates that the entity is under a pinhole camera at the given entity path that is not at the root of the space view. - pub parent_pinhole: Option, + pub parent_pinhole: EntityPath, + + /// The last 3D from 3D transform at the pinhole camera, before the pinhole transformation itself. + pub reference_from_pinhole_entity: glam::Affine3A, +} + +impl Default for TransformInfo { + fn default() -> Self { + Self { + reference_from_entity: glam::Affine3A::IDENTITY, + reference_from_instances: SmallVec1::new(glam::Affine3A::IDENTITY), + twod_in_threed_info: None, + } + } +} + +impl TransformInfo { + /// Warns that multiple transforms within the entity are not supported. + #[inline] + pub fn warn_on_per_instance_transform( + &self, + entity_name: &EntityPath, + visualizer_name: &'static str, + ) { + if self.reference_from_instances.len() > 1 { + re_log::warn_once!( + "There are multiple leaf transforms for entity {entity_name:?}. Visualizer {visualizer_name:?} supports only one transform per entity. Using the first one." + ); + } + } + + /// Returns the first instance transform and warns if there are multiple (via [`Self::warn_on_per_instance_transform`]). + #[inline] + pub fn single_entity_transform_required( + &self, + entity_name: &EntityPath, + visualizer_name: &'static str, + ) -> glam::Affine3A { + self.warn_on_per_instance_transform(entity_name, visualizer_name); + *self.reference_from_instances.first() + } + + /// Returns the first instance transform and does not warn if there are multiple. + #[inline] + pub fn single_entity_transform_silent(&self) -> glam::Affine3A { + *self.reference_from_instances.first() + } } #[derive(Clone, Copy)] @@ -43,6 +114,8 @@ enum UnreachableTransformReason { /// making world and reference space equivalent for a given space view. /// /// Should be recomputed every frame. +/// +/// TODO(#7025): Alternative proposal to not have to deal with tree upwards walking & per-origin tree walking. #[derive(Clone)] pub struct TransformContext { /// All transforms provided are relative to this reference path. @@ -103,7 +176,7 @@ impl ViewContextSystem for TransformContext { self.space_origin = query.space_origin.clone(); // Find the entity path tree for the root. - let Some(mut current_tree) = &entity_tree.subtree(query.space_origin) else { + let Some(current_tree) = &entity_tree.subtree(query.space_origin) else { // It seems the space path is not part of the object tree! // This happens frequently when the viewer remembers space views from a previous run that weren't shown yet. // Naturally, in this case we don't have any transforms yet. @@ -119,11 +192,32 @@ impl ViewContextSystem for TransformContext { current_tree, ctx.recording(), &time_query, - glam::Affine3A::IDENTITY, - &None, // Ignore potential pinhole camera at the root of the space view, since it regarded as being "above" this root. + // Ignore potential pinhole camera at the root of the space view, since it regarded as being "above" this root. + TransformInfo::default(), ); // Walk up from the reference to the highest reachable parent. + self.gather_parent_transforms(ctx, query, current_tree, &time_query); + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl TransformContext { + /// Gather transforms for everything _above_ the root. + fn gather_parent_transforms<'a>( + &mut self, + ctx: &'a ViewContext<'a>, + query: &re_viewer_context::ViewQuery<'_>, + mut current_tree: &'a EntityTree, + time_query: &LatestAtQuery, + ) { + re_tracing::profile_function!(); + + let entity_tree = ctx.recording().tree(); + let mut encountered_pinhole = None; let mut reference_from_ancestor = glam::Affine3A::IDENTITY; while let Some(parent_path) = current_tree.path.parent() { @@ -139,10 +233,10 @@ impl ViewContextSystem for TransformContext { // Note that the transform at the reference is the first that needs to be inverted to "break out" of its hierarchy. // Generally, the transform _at_ a node isn't relevant to it's children, but only to get to its parent in turn! - match transform_at( - current_tree, + let new_transform = match transforms_at( + ¤t_tree.path, ctx.recording(), - &time_query, + time_query, // TODO(#1025): See comment in transform_at. This is a workaround for precision issues // and the fact that there is no meaningful image plane distance for 3D->2D views. |_| 500.0, @@ -153,33 +247,28 @@ impl ViewContextSystem for TransformContext { Some((parent_tree.path.clone(), unreachable_reason)); break; } - Ok(None) => {} - Ok(Some(parent_from_child)) => { - reference_from_ancestor *= parent_from_child.inverse(); - } - } + Ok(transforms_at_entity) => transform_info_for_upward_propagation( + reference_from_ancestor, + transforms_at_entity, + ), + }; - // (skip over everything at and under `current_tree` automatically) + reference_from_ancestor = new_transform.reference_from_entity; + + // (this skips over everything at and under `current_tree` automatically) self.gather_descendants_transforms( ctx, query, parent_tree, ctx.recording(), - &time_query, - reference_from_ancestor, - &encountered_pinhole, + time_query, + new_transform, ); current_tree = parent_tree; } } - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -impl TransformContext { #[allow(clippy::too_many_arguments)] fn gather_descendants_transforms( &mut self, @@ -188,23 +277,21 @@ impl TransformContext { subtree: &EntityTree, entity_db: &EntityDb, query: &LatestAtQuery, - reference_from_entity: glam::Affine3A, - encountered_pinhole: &Option, + transform: TransformInfo, ) { + let twod_in_threed_info = transform.twod_in_threed_info.clone(); + let reference_from_parent = transform.reference_from_entity; match self.transform_per_entity.entry(subtree.path.clone()) { std::collections::hash_map::Entry::Occupied(_) => { return; } std::collections::hash_map::Entry::Vacant(e) => { - e.insert(TransformInfo { - reference_from_entity, - parent_pinhole: encountered_pinhole.clone(), - }); + e.insert(transform); } } for child_tree in subtree.children.values() { - let mut encountered_pinhole = encountered_pinhole.clone(); + let child_path = &child_tree.path; let lookup_image_plane = |p: &_| { let query_result = ctx.viewer_ctx.lookup_query_result(view_query.space_view_id); @@ -223,8 +310,11 @@ impl TransformContext { .into() }; - let reference_from_child = match transform_at( - child_tree, + let mut encountered_pinhole = twod_in_threed_info + .as_ref() + .map(|info| info.parent_pinhole.clone()); + let new_transform = match transforms_at( + child_path, entity_db, query, lookup_image_plane, @@ -232,20 +322,25 @@ impl TransformContext { ) { Err(unreachable_reason) => { self.unreachable_descendants - .push((child_tree.path.clone(), unreachable_reason)); + .push((child_path.clone(), unreachable_reason)); continue; } - Ok(None) => reference_from_entity, - Ok(Some(child_from_parent)) => reference_from_entity * child_from_parent, + + Ok(transforms_at_entity) => transform_info_for_downward_propagation( + child_path, + reference_from_parent, + twod_in_threed_info.clone(), + transforms_at_entity, + ), }; + self.gather_descendants_transforms( ctx, view_query, child_tree, entity_db, query, - reference_from_child, - &encountered_pinhole, + new_transform, ); } } @@ -254,51 +349,130 @@ impl TransformContext { &self.space_origin } - /// Retrieves the transform of on entity from its local system to the space of the reference. + /// Retrieves transform information for a given entity. /// - /// Returns None if the path is not reachable. - pub fn reference_from_entity(&self, ent_path: &EntityPath) -> Option { - self.transform_per_entity - .get(ent_path) - .map(|i| i.reference_from_entity) + /// Returns `None` if it's not reachable from the view's origin. + pub fn transform_info_for_entity(&self, ent_path: &EntityPath) -> Option<&TransformInfo> { + self.transform_per_entity.get(ent_path) } +} - /// Like [`Self::reference_from_entity`], but if `ent_path` has a pinhole camera, it won't affect the transform. - /// - /// Normally, the transform we compute for an entity with a pinhole transform places all objects - /// in front (defined by view coordinates) of the camera with a given image plane distance. - /// In some cases like drawing the lines for a frustum or arrows for the 3D transform, this is not the desired transformation. - /// Returns None if the path is not reachable. - /// - /// TODO(#2663, #1025): Going forward we should have separate transform hierarchies for 2D (i.e. projected) and 3D, - /// which would remove the need for this. - pub fn reference_from_entity_ignoring_pinhole( - &self, - ent_path: &EntityPath, - entity_db: &EntityDb, - query: &LatestAtQuery, - ) -> Option { - let transform_info = self.transform_per_entity.get(ent_path)?; - if let (true, Some(parent)) = ( - transform_info.parent_pinhole.as_ref() == Some(ent_path), - ent_path.parent(), +/// Compute transform info for when we walk up the tree from the reference. +fn transform_info_for_upward_propagation( + reference_from_ancestor: glam::Affine3A, + transforms_at_entity: TransformsAtEntity, +) -> TransformInfo { + let mut reference_from_entity = reference_from_ancestor; + + // Need to take care of the fact that we're walking the other direction of the tree here compared to `transform_info_for_downward_propagation`! + // Apply inverse transforms in flipped order! + + // Apply 2D->3D transform if present. + if let Some(entity_from_2d_pinhole_content) = + transforms_at_entity.instance_from_pinhole_image_plane + { + // If we're going up the tree and encounter a pinhole, we still to apply it. + // This is what handles "3D in 2D". + reference_from_entity *= entity_from_2d_pinhole_content.inverse(); + } + + // Collect & compute leaf transforms. + let (mut reference_from_instances, has_leaf_transforms) = if let Ok(mut entity_from_instances) = + SmallVec1::<[glam::Affine3A; 1]>::try_from_vec( + transforms_at_entity.entity_from_instance_leaf_transforms, ) { - self.reference_from_entity(&parent).map(|t| { - t * get_parent_from_child_transform(ent_path, entity_db, query).unwrap_or_default() - }) + for entity_from_instance in &mut entity_from_instances { + *entity_from_instance = reference_from_entity * entity_from_instance.inverse(); + // Now this is actually `reference_from_instance`. + } + (entity_from_instances, true) + } else { + (SmallVec1::new(reference_from_entity), false) + }; + + // Apply tree transform if any. + if let Some(parent_from_entity_tree_transform) = + transforms_at_entity.parent_from_entity_tree_transform + { + reference_from_entity *= parent_from_entity_tree_transform.inverse(); + if has_leaf_transforms { + for reference_from_instance in &mut reference_from_instances { + *reference_from_instance = reference_from_entity * (*reference_from_instance); + } } else { - Some(transform_info.reference_from_entity) + *reference_from_instances.first_mut() = reference_from_entity; } } - /// Retrieves the ancestor (or self) pinhole under which this entity sits. - /// - /// None indicates either that the entity does not exist in this hierarchy or that this entity is under the eye camera with no Pinhole camera in-between. - /// Some indicates that the entity is under a pinhole camera at the given entity path that is not at the root of the space view. - pub fn parent_pinhole(&self, ent_path: &EntityPath) -> Option<&EntityPath> { - self.transform_per_entity - .get(ent_path) - .and_then(|i| i.parent_pinhole.as_ref()) + TransformInfo { + reference_from_entity, + reference_from_instances, + + // Going up the tree, we can only encounter 2D->3D transforms. + // 3D->2D transforms can't happen because `Pinhole` represents 3D->2D (and we're walking backwards!) + twod_in_threed_info: None, + } +} + +/// Compute transform info for when we walk down the tree from the reference. +fn transform_info_for_downward_propagation( + current_path: &EntityPath, + reference_from_parent: glam::Affine3A, + mut twod_in_threed_info: Option, + transforms_at_entity: TransformsAtEntity, +) -> TransformInfo { + let mut reference_from_entity = reference_from_parent; + + // Apply tree transform. + if let Some(parent_from_entity_tree_transform) = + transforms_at_entity.parent_from_entity_tree_transform + { + reference_from_entity *= parent_from_entity_tree_transform; + } + + // Collect & compute leaf transforms. + let (mut reference_from_instances, has_leaf_transforms) = if let Ok(mut entity_from_instances) = + SmallVec1::try_from_vec(transforms_at_entity.entity_from_instance_leaf_transforms) + { + for entity_from_instance in &mut entity_from_instances { + *entity_from_instance = reference_from_entity * (*entity_from_instance); + // Now this is actually `reference_from_instance`. + } + (entity_from_instances, true) + } else { + (SmallVec1::new(reference_from_entity), false) + }; + + // Apply 2D->3D transform if present. + if let Some(entity_from_2d_pinhole_content) = + transforms_at_entity.instance_from_pinhole_image_plane + { + // Should have bailed out already earlier. + debug_assert!( + twod_in_threed_info.is_none(), + "2D->3D transform already set, this should be unreachable." + ); + + twod_in_threed_info = Some(TwoDInThreeDTransformInfo { + parent_pinhole: current_path.clone(), + reference_from_pinhole_entity: reference_from_entity, + }); + reference_from_entity *= entity_from_2d_pinhole_content; + + // Need to update per instance transforms as well if there are leaf transforms! + if has_leaf_transforms { + *reference_from_instances.first_mut() = reference_from_entity; + } else { + for reference_from_instance in &mut reference_from_instances { + *reference_from_instance *= entity_from_2d_pinhole_content; + } + } + } + + TransformInfo { + reference_from_entity, + reference_from_instances, + twod_in_threed_info, } } @@ -341,17 +515,24 @@ But they are instead ordered like this:\n{actual_order:?}" #[cfg(not(debug_assertions))] fn debug_assert_transform_field_order(_: &re_types::reflection::Reflection) {} -fn get_parent_from_child_transform( +fn query_and_resolve_tree_transform_at_entity( entity_path: &EntityPath, entity_db: &EntityDb, query: &LatestAtQuery, ) -> Option { + if !TransformComponentTracker::access(entity_db.store_id(), |tracker| { + tracker.is_potentially_transformed_transform3d(entity_path) + }) + .unwrap_or(false) + { + return None; + } + // TODO(#6743): Doesn't take into account overrides. let result = entity_db.latest_at( query, entity_path, [ - Transform3D::name(), Translation3D::name(), RotationAxisAngle::name(), RotationQuat::name(), @@ -364,17 +545,17 @@ fn get_parent_from_child_transform( return None; } - // Order is specified by order of components in the Transform3D archetype. - // See `has_transform_expected_order` let mut transform = glam::Affine3A::IDENTITY; + + // Order see `debug_assert_transform_field_order` if let Some(translation) = result.component_instance::(0) { - transform *= glam::Affine3A::from(translation); + transform = glam::Affine3A::from(translation); } - if let Some(rotation) = result.component_instance::(0) { - transform *= glam::Affine3A::from(rotation); + if let Some(rotation_quat) = result.component_instance::(0) { + transform *= glam::Affine3A::from(rotation_quat); } - if let Some(rotation) = result.component_instance::(0) { - transform *= glam::Affine3A::from(rotation); + if let Some(rotation_axis_angle) = result.component_instance::(0) { + transform *= glam::Affine3A::from(rotation_axis_angle); } if let Some(scale) = result.component_instance::(0) { transform *= glam::Affine3A::from(scale); @@ -382,25 +563,128 @@ fn get_parent_from_child_transform( if let Some(mat3x3) = result.component_instance::(0) { transform *= glam::Affine3A::from(mat3x3); } + if result.component_instance::(0) == Some(TransformRelation::ChildFromParent) + { + transform = transform.inverse(); + } - let transform_relation = result - .component_instance::(0) - .unwrap_or_default(); - if transform_relation == TransformRelation::ChildFromParent { - Some(transform.inverse()) - } else { - Some(transform) + Some(transform) +} + +fn query_and_resolve_leaf_transform_at_entity( + entity_path: &EntityPath, + entity_db: &EntityDb, + query: &LatestAtQuery, +) -> Vec { + if !TransformComponentTracker::access(entity_db.store_id(), |tracker| { + tracker.is_potentially_transformed_leaf_transform3d(entity_path) + }) + .unwrap_or(false) + { + return Vec::new(); + } + + // TODO(#6743): Doesn't take into account overrides. + let result = entity_db.latest_at( + query, + entity_path, + [ + LeafTranslation3D::name(), + LeafRotationAxisAngle::name(), + LeafRotationQuat::name(), + LeafScale3D::name(), + LeafTransformMat3x3::name(), + ], + ); + + let max_count = result + .components + .values() + .map(|comp| comp.num_instances()) + .max() + .unwrap_or(0) as usize; + if max_count == 0 { + return Vec::new(); + } + + #[inline] + pub fn clamped_or_nothing( + values: Vec, + clamped_len: usize, + ) -> impl Iterator { + let Some(last) = values.last() else { + return Either::Left(std::iter::empty()); + }; + let last = last.clone(); + Either::Right( + values + .into_iter() + .chain(std::iter::repeat(last)) + .take(clamped_len), + ) + } + + let mut iter_translation = clamped_or_nothing( + result + .component_batch::() + .unwrap_or_default(), + max_count, + ); + let mut iter_rotation_quat = clamped_or_nothing( + result + .component_batch::() + .unwrap_or_default(), + max_count, + ); + let mut iter_rotation_axis_angle = clamped_or_nothing( + result + .component_batch::() + .unwrap_or_default(), + max_count, + ); + let mut iter_scale = clamped_or_nothing( + result.component_batch::().unwrap_or_default(), + max_count, + ); + let mut iter_mat3x3 = clamped_or_nothing( + result + .component_batch::() + .unwrap_or_default(), + max_count, + ); + + let mut transforms = Vec::with_capacity(max_count); + for _ in 0..max_count { + // Order see `debug_assert_transform_field_order` + let mut transform = glam::Affine3A::IDENTITY; + if let Some(translation) = iter_translation.next() { + transform = glam::Affine3A::from(translation); + } + if let Some(rotation_quat) = iter_rotation_quat.next() { + transform *= glam::Affine3A::from(rotation_quat); + } + if let Some(rotation_axis_angle) = iter_rotation_axis_angle.next() { + transform *= glam::Affine3A::from(rotation_axis_angle); + } + if let Some(scale) = iter_scale.next() { + transform *= glam::Affine3A::from(scale); + } + if let Some(mat3x3) = iter_mat3x3.next() { + transform *= glam::Affine3A::from(mat3x3); + } + + transforms.push(transform); } - // TODO(#6831): Should add a unit test to this method once all variants are in. - // (Should test correct order being applied etc.. Might require splitting) + transforms } -fn get_cached_pinhole( - entity_path: &re_log_types::EntityPath, +fn query_and_resolve_obj_from_pinhole_image_plane( + entity_path: &EntityPath, entity_db: &EntityDb, - query: &re_chunk_store::LatestAtQuery, -) -> Option<(PinholeProjection, ViewCoordinates)> { + query: &LatestAtQuery, + pinhole_image_plane_distance: impl Fn(&EntityPath) -> f32, +) -> Option { entity_db .latest_at_component::(entity_path, query) .map(|(_index, image_from_camera)| { @@ -411,21 +695,84 @@ fn get_cached_pinhole( .map_or(ViewCoordinates::RDF, |(_index, res)| res), ) }) + .map(|(image_from_camera, view_coordinates)| { + // Everything under a pinhole camera is a 2D projection, thus doesn't actually have a proper 3D representation. + // Our visualization interprets this as looking at a 2D image plane from a single point (the pinhole). + + // Center the image plane and move it along z, scaling the further the image plane is. + let distance = pinhole_image_plane_distance(entity_path); + let focal_length = image_from_camera.focal_length_in_pixels(); + let focal_length = glam::vec2(focal_length.x(), focal_length.y()); + let scale = distance / focal_length; + let translation = (-image_from_camera.principal_point() * scale).extend(distance); + + let image_plane3d_from_2d_content = glam::Affine3A::from_translation(translation) + // We want to preserve any depth that might be on the pinhole image. + // Use harmonic mean of x/y scale for those. + * glam::Affine3A::from_scale( + scale.extend(2.0 / (1.0 / scale.x + 1.0 / scale.y)), + ); + + // Our interpretation of the pinhole camera implies that the axis semantics, i.e. ViewCoordinates, + // determine how the image plane is oriented. + // (see also `CamerasPart` where the frustum lines are set up) + let obj_from_image_plane3d = view_coordinates.from_other(&image_view_coordinates()); + + glam::Affine3A::from_mat3(obj_from_image_plane3d) * image_plane3d_from_2d_content + + // Above calculation is nice for a certain kind of visualizing a projected image plane, + // but the image plane distance is arbitrary and there might be other, better visualizations! + + // TODO(#1025): + // As such we don't ever want to invert this matrix! + // However, currently our 2D views require do to exactly that since we're forced to + // build a relationship between the 2D plane and the 3D world, when actually the 2D plane + // should have infinite depth! + // The inverse of this matrix *is* working for this, but quickly runs into precision issues. + // See also `ui_2d.rs#setup_target_config` + }) +} + +/// Resolved transforms at an entity. +struct TransformsAtEntity { + parent_from_entity_tree_transform: Option, + entity_from_instance_leaf_transforms: Vec, + instance_from_pinhole_image_plane: Option, } -fn transform_at( - subtree: &EntityTree, +fn transforms_at( + entity_path: &EntityPath, entity_db: &EntityDb, query: &LatestAtQuery, pinhole_image_plane_distance: impl Fn(&EntityPath) -> f32, encountered_pinhole: &mut Option, -) -> Result, UnreachableTransformReason> { +) -> Result { re_tracing::profile_function!(); - let entity_path = &subtree.path; + let transforms_at_entity = TransformsAtEntity { + parent_from_entity_tree_transform: query_and_resolve_tree_transform_at_entity( + entity_path, + entity_db, + query, + ), + entity_from_instance_leaf_transforms: query_and_resolve_leaf_transform_at_entity( + entity_path, + entity_db, + query, + ), + instance_from_pinhole_image_plane: query_and_resolve_obj_from_pinhole_image_plane( + entity_path, + entity_db, + query, + pinhole_image_plane_distance, + ), + }; - let pinhole = get_cached_pinhole(entity_path, entity_db, query); - if pinhole.is_some() { + // Handle pinhole encounters. + if transforms_at_entity + .instance_from_pinhole_image_plane + .is_some() + { if encountered_pinhole.is_some() { return Err(UnreachableTransformReason::NestedPinholeCameras); } else { @@ -433,72 +780,22 @@ fn transform_at( } } - // If this entity does not contain any `Transform3D`-related data at all, there's no - // point in running actual queries. - let is_potentially_transformed = - crate::transform_component_tracker::TransformComponentTracker::access( - entity_db.store_id(), - |transform_component_tracker| { - transform_component_tracker.is_potentially_transformed(entity_path) - }, - ) - .unwrap_or(false); - let transform3d = is_potentially_transformed - .then(|| get_parent_from_child_transform(entity_path, entity_db, query)) - .flatten(); - - let pinhole = pinhole.map(|(image_from_camera, camera_xyz)| { - // Everything under a pinhole camera is a 2D projection, thus doesn't actually have a proper 3D representation. - // Our visualization interprets this as looking at a 2D image plane from a single point (the pinhole). - - // Center the image plane and move it along z, scaling the further the image plane is. - let distance = pinhole_image_plane_distance(entity_path); - let focal_length = image_from_camera.focal_length_in_pixels(); - let focal_length = glam::vec2(focal_length.x(), focal_length.y()); - let scale = distance / focal_length; - let translation = (-image_from_camera.principal_point() * scale).extend(distance); - - let image_plane3d_from_2d_content = glam::Affine3A::from_translation(translation) - // We want to preserve any depth that might be on the pinhole image. - // Use harmonic mean of x/y scale for those. - * glam::Affine3A::from_scale( - scale.extend(2.0 / (1.0 / scale.x + 1.0 / scale.y)), - ); - - // Our interpretation of the pinhole camera implies that the axis semantics, i.e. ViewCoordinates, - // determine how the image plane is oriented. - // (see also `CamerasPart` where the frustum lines are set up) - let world_from_image_plane3d = camera_xyz.from_other(&image_view_coordinates()); - - glam::Affine3A::from_mat3(world_from_image_plane3d) * image_plane3d_from_2d_content - - // Above calculation is nice for a certain kind of visualizing a projected image plane, - // but the image plane distance is arbitrary and there might be other, better visualizations! - - // TODO(#1025): - // As such we don't ever want to invert this matrix! - // However, currently our 2D views require do to exactly that since we're forced to - // build a relationship between the 2D plane and the 3D world, when actually the 2D plane - // should have infinite depth! - // The inverse of this matrix *is* working for this, but quickly runs into precision issues. - // See also `ui_2d.rs#setup_target_config` - }); - - let is_disconnect_space = || { - entity_db + // If there is any other transform, we ignore `DisconnectedSpace`. + if transforms_at_entity + .parent_from_entity_tree_transform + .is_none() + && transforms_at_entity + .entity_from_instance_leaf_transforms + .is_empty() + && transforms_at_entity + .instance_from_pinhole_image_plane + .is_none() + && entity_db .latest_at_component::(entity_path, query) .map_or(false, |(_index, res)| **res) - }; - - // If there is any other transform, we ignore `DisconnectedSpace`. - if transform3d.is_some() || pinhole.is_some() { - Ok(Some( - transform3d.unwrap_or(glam::Affine3A::IDENTITY) - * pinhole.unwrap_or(glam::Affine3A::IDENTITY), - )) - } else if is_disconnect_space() { + { Err(UnreachableTransformReason::DisconnectedSpace) } else { - Ok(None) + Ok(transforms_at_entity) } } diff --git a/crates/viewer/re_space_view_spatial/src/mesh_loader.rs b/crates/viewer/re_space_view_spatial/src/mesh_loader.rs index df0a01b68c11..1949cdbe4b1b 100644 --- a/crates/viewer/re_space_view_spatial/src/mesh_loader.rs +++ b/crates/viewer/re_space_view_spatial/src/mesh_loader.rs @@ -73,11 +73,7 @@ impl LoadedMesh { ) -> anyhow::Result { re_tracing::profile_function!(); - let Asset3D { - blob, - media_type, - transform: _, - } = asset3d; + let Asset3D { blob, media_type } = asset3d; let media_type = MediaType::or_guess_from_data(media_type.clone(), blob.as_slice()) .ok_or_else(|| anyhow::anyhow!("couldn't guess media type"))?; diff --git a/crates/viewer/re_space_view_spatial/src/transform_component_tracker.rs b/crates/viewer/re_space_view_spatial/src/transform_component_tracker.rs index adb8a741b895..de0879efe830 100644 --- a/crates/viewer/re_space_view_spatial/src/transform_component_tracker.rs +++ b/crates/viewer/re_space_view_spatial/src/transform_component_tracker.rs @@ -6,7 +6,7 @@ use re_chunk_store::{ ChunkStore, ChunkStoreDiffKind, ChunkStoreEvent, ChunkStoreSubscriber, ChunkStoreSubscriberHandle, }; -use re_log_types::{EntityPath, StoreId}; +use re_log_types::{EntityPath, EntityPathHash, StoreId}; use re_types::ComponentName; // --- @@ -20,8 +20,11 @@ use re_types::ComponentName; /// This is a huge performance improvement in practice, especially in recordings with many entities. #[derive(Default)] pub struct TransformComponentTracker { - /// Which entities have had any of these components at any point in time. - entities: IntSet, + /// Which entities have had any `Transform3D` component at any point in time. + transform3d_entities: IntSet, + + /// Which entities have had any `LeafTransforms3D` components at any point in time. + leaf_transforms3d_entities: IntSet, } impl TransformComponentTracker { @@ -38,8 +41,14 @@ impl TransformComponentTracker { } #[inline] - pub fn is_potentially_transformed(&self, entity_path: &EntityPath) -> bool { - self.entities.contains(entity_path) + pub fn is_potentially_transformed_transform3d(&self, entity_path: &EntityPath) -> bool { + self.transform3d_entities.contains(&entity_path.hash()) + } + + #[inline] + pub fn is_potentially_transformed_leaf_transform3d(&self, entity_path: &EntityPath) -> bool { + self.leaf_transforms3d_entities + .contains(&entity_path.hash()) } } @@ -47,7 +56,8 @@ impl TransformComponentTracker { pub struct TransformComponentTrackerStoreSubscriber { /// The components of interest. - components: IntSet, + transform_components: IntSet, + leaf_transform_components: IntSet, per_store: HashMap, } @@ -56,13 +66,15 @@ impl Default for TransformComponentTrackerStoreSubscriber { #[inline] fn default() -> Self { use re_types::Archetype as _; - let components = re_types::archetypes::Transform3D::all_components() - .iter() - .copied() - .collect(); - Self { - components, + transform_components: re_types::archetypes::Transform3D::all_components() + .iter() + .copied() + .collect(), + leaf_transform_components: re_types::archetypes::LeafTransforms3D::all_components() + .iter() + .copied() + .collect(), per_store: Default::default(), } } @@ -105,11 +117,18 @@ impl ChunkStoreSubscriber for TransformComponentTrackerStoreSubscriber { let transform_component_tracker = self.per_store.entry(event.store_id.clone()).or_default(); + let entity_path_hash = event.chunk.entity_path().hash(); + for component_name in event.chunk.component_names() { - if self.components.contains(&component_name) { + if self.transform_components.contains(&component_name) { + transform_component_tracker + .transform3d_entities + .insert(entity_path_hash); + } + if self.leaf_transform_components.contains(&component_name) { transform_component_tracker - .entities - .insert(event.chunk.entity_path().clone()); + .leaf_transforms3d_entities + .insert(entity_path_hash); } } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/arrows2d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/arrows2d.rs index f0c50c25ff0a..3b2a5d4495cc 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/arrows2d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/arrows2d.rs @@ -72,9 +72,13 @@ impl Arrows2DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Arrows2D"); + let mut line_batch = line_builder .batch(entity_path.to_string()) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -112,11 +116,8 @@ impl Arrows2DVisualizer { obj_space_bounding_box.extend(end.extend(0.0)); } - self.data.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.data + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { // If there's many arrows but only a single label, place the single label at the middle of the visualization. @@ -142,7 +143,7 @@ impl Arrows2DVisualizer { data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs index 0242fc0ff2cf..291ddf5d34d0 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs @@ -72,9 +72,13 @@ impl Arrows3DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Arrows3D"); + let mut line_batch = line_builder .batch(entity_path.to_string()) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -113,11 +117,8 @@ impl Arrows3DVisualizer { obj_space_bounding_box.extend(end); } - self.data.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.data + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { // If there's many arrows but only a single label, place the single label at the middle of the visualization. @@ -142,7 +143,7 @@ impl Arrows3DVisualizer { data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs index fce71efc35ae..4b71591cf8d9 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs @@ -1,12 +1,10 @@ -use itertools::Itertools as _; - use re_chunk_store::RowId; use re_log_types::{hash::Hash64, Instance, TimeInt}; use re_renderer::renderer::MeshInstance; use re_renderer::RenderContext; use re_types::{ archetypes::Asset3D, - components::{Blob, MediaType, OutOfTreeTransform3D}, + components::{Blob, MediaType}, ArrowBuffer, ArrowString, Loggable as _, }; use re_viewer_context::{ @@ -34,24 +32,23 @@ impl Default for Asset3DVisualizer { } } -struct Asset3DComponentData<'a> { +struct Asset3DComponentData { index: (TimeInt, RowId), blob: ArrowBuffer, media_type: Option, - transform: Option<&'a OutOfTreeTransform3D>, } // NOTE: Do not put profile scopes in these methods. They are called for all entities and all // timestamps within a time range -- it's _a lot_. impl Asset3DVisualizer { - fn process_data<'a>( + fn process_data( &mut self, ctx: &QueryContext<'_>, render_ctx: &RenderContext, instances: &mut Vec, ent_context: &SpatialSceneEntityContext<'_>, - data: impl Iterator>, + data: impl Iterator, ) { let entity_path = ctx.target_entity_path; @@ -59,9 +56,6 @@ impl Asset3DVisualizer { let mesh = Asset3D { blob: data.blob.clone().into(), media_type: data.media_type.clone().map(Into::into), - - // NOTE: Don't even try to cache the transform! - transform: None, }; let primary_row_id = data.index.1; @@ -87,28 +81,27 @@ impl Asset3DVisualizer { if let Some(mesh) = mesh { re_tracing::profile_scope!("mesh instances"); - let world_from_pose = ent_context.world_from_entity - * data - .transform - .map_or(glam::Affine3A::IDENTITY, |t| t.0.into()); - - instances.extend(mesh.mesh_instances.iter().map(move |mesh_instance| { - let pose_from_mesh = mesh_instance.world_from_mesh; - let world_from_mesh = world_from_pose * pose_from_mesh; - - MeshInstance { - gpu_mesh: mesh_instance.gpu_mesh.clone(), - world_from_mesh, - outline_mask_ids, - picking_layer_id: picking_layer_id_from_instance_path_hash( - picking_instance_hash, - ), - ..Default::default() - } - })); - - self.0 - .add_bounding_box(entity_path.hash(), mesh.bbox(), world_from_pose); + // Let's draw the mesh once for every instance transform. + // TODO(#7026): This a rare form of hybrid joining. + for &world_from_pose in &ent_context.transform_info.reference_from_instances { + instances.extend(mesh.mesh_instances.iter().map(move |mesh_instance| { + let pose_from_mesh = mesh_instance.world_from_mesh; + let world_from_mesh = world_from_pose * pose_from_mesh; + + MeshInstance { + gpu_mesh: mesh_instance.gpu_mesh.clone(), + world_from_mesh, + outline_mask_ids, + picking_layer_id: picking_layer_id_from_instance_path_hash( + picking_instance_hash, + ), + ..Default::default() + } + })); + + self.0 + .add_bounding_box(entity_path.hash(), mesh.bbox(), world_from_pose); + } }; } } @@ -166,37 +159,15 @@ impl VisualizerSystem for Asset3DVisualizer { }); let all_media_types = results.iter_as(timeline, MediaType::name()); - // TODO(#6831): we have to deserialize here because `OutOfTreeTransform3D` is - // still a complex type at this point. - let all_transform_chunks = - results.get_optional_chunks(&OutOfTreeTransform3D::name()); - let mut all_transform_iters = all_transform_chunks - .iter() - .map(|chunk| chunk.iter_component::()) - .collect_vec(); - let all_transforms_indexed = { - let all_transforms = - all_transform_iters.iter_mut().flat_map(|it| it.into_iter()); - let all_transforms_indices = all_transform_chunks.iter().flat_map(|chunk| { - chunk.iter_component_indices(&timeline, &OutOfTreeTransform3D::name()) + let data = re_query2::range_zip_1x1(all_blobs_indexed, all_media_types.string()) + .filter_map(|(index, blobs, media_types)| { + blobs.first().map(|blob| Asset3DComponentData { + index, + blob: blob.clone(), + media_type: media_types + .and_then(|media_types| media_types.first().cloned()), + }) }); - itertools::izip!(all_transforms_indices, all_transforms) - }; - - let data = re_query2::range_zip_1x2( - all_blobs_indexed, - all_media_types.string(), - all_transforms_indexed, - ) - .filter_map(|(index, blobs, media_types, transforms)| { - blobs.first().map(|blob| Asset3DComponentData { - index, - blob: blob.clone(), - media_type: media_types - .and_then(|media_types| media_types.first().cloned()), - transform: transforms.and_then(|transforms| transforms.first()), - }) - }); self.process_data(ctx, render_ctx, &mut instances, spatial_ctx, data); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes2d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes2d.rs index 70844766741e..86e4d41f843f 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes2d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes2d.rs @@ -121,10 +121,14 @@ impl Boxes2DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Boxes2D"); + let mut line_batch = line_builder .batch("boxes2d") .depth_offset(ent_context.depth_offset) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -158,11 +162,8 @@ impl Boxes2DVisualizer { } } - self.data.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.data + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { if data.labels.len() == 1 && num_instances > 1 { @@ -174,7 +175,7 @@ impl Boxes2DVisualizer { data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } else { let centers = clamped_or(data.centers, &Position2D::ZERO); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index bcbd75e16a46..295410444b50 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -78,10 +78,14 @@ impl Boxes3DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Boxes3D"); + let mut line_batch = line_builder .batch("boxes3d") .depth_offset(ent_context.depth_offset) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -150,11 +154,8 @@ impl Boxes3DVisualizer { } } - self.0.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.0 + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { // If there's many boxes but only a single label, place the single label at the middle of the visualization. @@ -174,7 +175,7 @@ impl Boxes3DVisualizer { data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs b/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs index 5ac201daa5de..8b42bac3fc6c 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/cameras.rs @@ -3,7 +3,7 @@ use re_log_types::Instance; use re_renderer::renderer::LineStripFlags; use re_types::{ archetypes::Pinhole, - components::{ImagePlaneDistance, Transform3D, ViewCoordinates}, + components::{ImagePlaneDistance, ViewCoordinates}, }; use re_viewer_context::{ ApplicableEntities, DataResult, IdentifiedViewSystem, QueryContext, SpaceViewOutlineMasks, @@ -52,7 +52,6 @@ impl CamerasVisualizer { transforms: &TransformContext, data_result: &DataResult, pinhole: &Pinhole, - transform_at_entity: Option, pinhole_view_coordinates: ViewCoordinates, entity_highlight: &SpaceViewOutlineMasks, ) { @@ -74,42 +73,37 @@ impl CamerasVisualizer { return; } - // We need special handling to find the 3D transform for drawing the - // frustum itself. The transform that would otherwise be in the - // transform context might include both a rigid transform and a pinhole. This - // makes sense, since if there's an image logged here one would expect - // both the rigid and the pinhole to apply, but here we're only interested - // in the rigid transform at this entity path, excluding the pinhole - // portion (we handle the pinhole separately later). - let world_from_camera_rigid = { - // Start with the transform to the entity parent, if it exists - let world_from_parent = ent_path - .parent() - .and_then(|parent_path| transforms.reference_from_entity(&parent_path)) - .unwrap_or(glam::Affine3A::IDENTITY); - - // Then combine it with the transform at the entity itself, if there is one. - if let Some(transform_at_entity) = transform_at_entity { - world_from_parent * transform_at_entity.into_parent_from_child_transform() - } else { - world_from_parent - } + // The camera transform does not include the pinhole transform. + let Some(transform_info) = transforms.transform_info_for_entity(ent_path) else { + return; + }; + let Some(twod_in_threed_info) = &transform_info.twod_in_threed_info else { + // This implies that the transform context didn't see the pinhole transform. + // Should be impossible! + re_log::error_once!("Transform context didn't register the pinhole transform, but `CamerasVisualizer` is trying to display it!"); + return; }; + if &twod_in_threed_info.parent_pinhole != ent_path { + // This implies that the camera is under another camera. + // This should be reported already as an error at this point. + return; + } + let world_from_camera = twod_in_threed_info.reference_from_pinhole_entity; // If this transform is not representable as an `IsoTransform` we can't display it yet. // This would happen if the camera is under another camera or under a transform with non-uniform scale. - let Some(world_from_camera_rigid_iso) = - re_math::IsoTransform::from_mat4(&world_from_camera_rigid.into()) + let Some(world_from_camera_iso) = + re_math::IsoTransform::from_mat4(&world_from_camera.into()) else { return; }; - debug_assert!(world_from_camera_rigid_iso.is_finite()); + debug_assert!(world_from_camera_iso.is_finite()); self.space_cameras.push(SpaceCamera3D { ent_path: ent_path.clone(), pinhole_view_coordinates, - world_from_camera: world_from_camera_rigid_iso, + world_from_camera: world_from_camera_iso, pinhole: Some(pinhole.clone()), picture_plane_distance: frustum_length, }); @@ -163,8 +157,7 @@ impl CamerasVisualizer { // The frustum is setup as a RDF frustum, but if the view coordinates are not RDF, // we need to reorient the displayed frustum so that we indicate the correct orientation in the 3D world space. .world_from_obj( - world_from_camera_rigid - * glam::Affine3A::from_mat3(pinhole_view_coordinates.from_rdf()), + world_from_camera * glam::Affine3A::from_mat3(pinhole_view_coordinates.from_rdf()), ) .outline_mask_ids(entity_highlight.overall) .picking_object_id(instance_layer_id.object); @@ -183,7 +176,7 @@ impl CamerasVisualizer { self.data.add_bounding_box_from_points( ent_path.hash(), std::iter::once(glam::Vec3::ZERO), - world_from_camera_rigid, + world_from_camera, ); } } @@ -232,10 +225,6 @@ impl VisualizerSystem for CamerasVisualizer { transforms, data_result, &pinhole, - // TODO(#5607): what should happen if the promise is still pending? - ctx.recording() - .latest_at_component::(&data_result.entity_path, &time_query) - .map(|(_index, c)| c), pinhole.camera_xyz.unwrap_or(ViewCoordinates::RDF), // TODO(#2641): This should come from archetype entity_highlight, ); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs b/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs index e02ec82d72db..5aacc564fb7c 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/depth_images.rs @@ -17,14 +17,15 @@ use re_viewer_context::{ }; use crate::{ - contexts::{SpatialSceneEntityContext, TransformContext}, + contexts::SpatialSceneEntityContext, + contexts::TwoDInThreeDTransformInfo, query_pinhole_legacy, view_kind::SpatialSpaceViewKind, visualizers::{filter_visualizable_2d_entities, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES}, - PickableImageRect, SpatialSpaceView2D, SpatialSpaceView3D, + PickableImageRect, SpatialSpaceView3D, }; -use super::{bounding_box_for_textured_rect, textured_rect_from_image, SpatialViewVisualizerData}; +use super::{textured_rect_from_image, SpatialViewVisualizerData}; pub struct DepthImageVisualizer { pub data: SpatialViewVisualizerData, @@ -53,12 +54,14 @@ impl DepthImageVisualizer { &mut self, ctx: &QueryContext<'_>, depth_clouds: &mut Vec, - transforms: &TransformContext, ent_context: &SpatialSceneEntityContext<'_>, images: impl Iterator, ) { let is_3d_view = ent_context.space_view_class_identifier == SpatialSpaceView3D::identifier(); + ent_context + .transform_info + .warn_on_per_instance_transform(ctx.target_entity_path, "DepthImage"); let entity_path = ctx.target_entity_path; let meaning = TensorDataMeaning::Depth; @@ -76,7 +79,7 @@ impl DepthImageVisualizer { image.colormap = Some(image.colormap.unwrap_or_else(|| self.fallback_for(ctx))); if is_3d_view { - if let Some(parent_pinhole_path) = transforms.parent_pinhole(entity_path) { + if let Some(twod_in_threed_info) = &ent_context.transform_info.twod_in_threed_info { let fill_ratio = fill_ratio.unwrap_or_default(); // NOTE: we don't pass in `world_from_obj` because this corresponds to the @@ -84,11 +87,10 @@ impl DepthImageVisualizer { // What we want are the extrinsics of the depth camera! match Self::process_entity_view_as_depth_cloud( ctx, - transforms, ent_context, &image, entity_path, - parent_pinhole_path, + twod_in_threed_info, depth_meter, fill_ratio, ) { @@ -116,19 +118,9 @@ impl DepthImageVisualizer { &image, meaning, re_renderer::Rgba::WHITE, + "DepthImage", + &mut self.data, ) { - // Only update the bounding box if this is a 2D space view. - // This is avoids a cyclic relationship where the image plane grows - // the bounds which in turn influence the size of the image plane. - // See: https://github.com/rerun-io/rerun/issues/3728 - if ent_context.space_view_class_identifier == SpatialSpaceView2D::identifier() { - self.data.add_bounding_box( - entity_path.hash(), - bounding_box_for_textured_rect(&textured_rect), - ent_context.world_from_entity, - ); - } - self.images.push(PickableImageRect { ent_path: entity_path.clone(), row_id: image.blob_row_id, @@ -142,34 +134,30 @@ impl DepthImageVisualizer { } } - #[allow(clippy::too_many_arguments)] fn process_entity_view_as_depth_cloud( ctx: &QueryContext<'_>, - transforms: &TransformContext, ent_context: &SpatialSceneEntityContext<'_>, image: &ImageInfo, ent_path: &EntityPath, - parent_pinhole_path: &EntityPath, + twod_in_threed_info: &TwoDInThreeDTransformInfo, depth_meter: DepthMeter, radius_scale: FillRatio, ) -> anyhow::Result { re_tracing::profile_function!(); - let Some(intrinsics) = - query_pinhole_legacy(ctx.recording(), ctx.query, parent_pinhole_path) - else { - anyhow::bail!("Couldn't fetch pinhole intrinsics at {parent_pinhole_path:?}"); - }; - - // Place the cloud at the pinhole's location. Note that this means we ignore any 2D transforms that might be there. - let world_from_view = transforms.reference_from_entity_ignoring_pinhole( - parent_pinhole_path, + let Some(intrinsics) = query_pinhole_legacy( ctx.recording(), ctx.query, - ); - let Some(world_from_view) = world_from_view else { - anyhow::bail!("Couldn't fetch pinhole extrinsics at {parent_pinhole_path:?}"); + &twod_in_threed_info.parent_pinhole, + ) else { + anyhow::bail!( + "Couldn't fetch pinhole intrinsics at {:?}", + twod_in_threed_info.parent_pinhole + ); }; + + // Place the cloud at the pinhole's location. Note that this means we ignore any 2D transforms that might be there. + let world_from_view = twod_in_threed_info.reference_from_pinhole_entity; let world_from_rdf = world_from_view * glam::Affine3A::from_mat3( intrinsics @@ -255,7 +243,6 @@ impl VisualizerSystem for DepthImageVisualizer { }; let mut depth_clouds = Vec::new(); - let transforms = context_systems.get::()?; super::entity_iterator::process_archetype::( ctx, @@ -314,13 +301,7 @@ impl VisualizerSystem for DepthImageVisualizer { }, ); - self.process_depth_image_data( - ctx, - &mut depth_clouds, - transforms, - spatial_ctx, - &mut data, - ); + self.process_depth_image_data(ctx, &mut depth_clouds, spatial_ctx, &mut data); Ok(()) }, diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs index b5a6fbdcd9ac..fbc853a1396d 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -117,19 +117,23 @@ impl EllipsoidsVisualizer { let centers = clamped_or(data.centers, &Position3D::ZERO); let rotations = clamped_or(data.rotations, &Rotation3D::IDENTITY); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Ellipsoids"); + self.0.ui_labels.extend(Self::process_labels( entity_path, data.centers, data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); let mut line_batch = line_builder .batch("ellipsoids") .depth_offset(ent_context.depth_offset) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -219,11 +223,8 @@ impl EllipsoidsVisualizer { } } - self.0.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.0 + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); } Ok(()) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/image_encoded.rs b/crates/viewer/re_space_view_spatial/src/visualizers/image_encoded.rs index 0f1aa1fe078f..82afabded9ac 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/image_encoded.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/image_encoded.rs @@ -8,7 +8,7 @@ use re_types::{ tensor_data::TensorDataMeaning, }; use re_viewer_context::{ - ApplicableEntities, IdentifiedViewSystem, ImageDecodeCache, QueryContext, SpaceViewClass, + ApplicableEntities, IdentifiedViewSystem, ImageDecodeCache, QueryContext, SpaceViewSystemExecutionError, TypedComponentFallbackProvider, ViewContext, ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, VisualizerQueryInfo, VisualizerSystem, @@ -16,12 +16,11 @@ use re_viewer_context::{ use crate::{ contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind, - visualizers::filter_visualizable_2d_entities, PickableImageRect, SpatialSpaceView2D, + visualizers::filter_visualizable_2d_entities, PickableImageRect, }; use super::{ - bounding_box_for_textured_rect, entity_iterator::process_archetype, textured_rect_from_tensor, - SpatialViewVisualizerData, + entity_iterator::process_archetype, textured_rect_from_tensor, SpatialViewVisualizerData, }; pub struct ImageEncodedVisualizer { @@ -188,19 +187,9 @@ impl ImageEncodedVisualizer { meaning, multiplicative_tint, colormap, + "ImageEncoded", + &mut self.data, ) { - // Only update the bounding box if this is a 2D space view. - // This is avoids a cyclic relationship where the image plane grows - // the bounds which in turn influence the size of the image plane. - // See: https://github.com/rerun-io/rerun/issues/3728 - if spatial_ctx.space_view_class_identifier == SpatialSpaceView2D::identifier() { - self.data.add_bounding_box( - entity_path.hash(), - bounding_box_for_textured_rect(&textured_rect), - spatial_ctx.world_from_entity, - ); - } - self.images.push(PickableImageRect { ent_path: entity_path.clone(), row_id: tensor_data_row_id, diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/images.rs b/crates/viewer/re_space_view_spatial/src/visualizers/images.rs index 67d8bfa8bad7..8ffd7bd2a2f3 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/images.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/images.rs @@ -10,20 +10,19 @@ use re_types::{ tensor_data::TensorDataMeaning, }; use re_viewer_context::{ - ApplicableEntities, IdentifiedViewSystem, QueryContext, SpaceViewClass, - SpaceViewSystemExecutionError, TypedComponentFallbackProvider, ViewContext, - ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, - VisualizerAdditionalApplicabilityFilter, VisualizerQueryInfo, VisualizerSystem, + ApplicableEntities, IdentifiedViewSystem, QueryContext, SpaceViewSystemExecutionError, + TypedComponentFallbackProvider, ViewContext, ViewContextCollection, ViewQuery, + VisualizableEntities, VisualizableFilterContext, VisualizerAdditionalApplicabilityFilter, + VisualizerQueryInfo, VisualizerSystem, }; use crate::{ contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind, - visualizers::filter_visualizable_2d_entities, PickableImageRect, SpatialSpaceView2D, + visualizers::filter_visualizable_2d_entities, PickableImageRect, }; use super::{ - bounding_box_for_textured_rect, entity_iterator::process_archetype, textured_rect_from_tensor, - SpatialViewVisualizerData, + entity_iterator::process_archetype, textured_rect_from_tensor, SpatialViewVisualizerData, }; pub struct ImageVisualizer { @@ -178,6 +177,10 @@ impl ImageVisualizer { // TODO(jleibs): Make this more explicit let meaning = TensorDataMeaning::Unknown; + spatial_ctx + .transform_info + .warn_on_per_instance_transform(ctx.target_entity_path, "DepthImage"); + for data in data { if !data.tensor.is_shaped_like_an_image() { continue; @@ -205,19 +208,9 @@ impl ImageVisualizer { meaning, multiplicative_tint, colormap, + "Image", + &mut self.data, ) { - // Only update the bounding box if this is a 2D space view. - // This is avoids a cyclic relationship where the image plane grows - // the bounds which in turn influence the size of the image plane. - // See: https://github.com/rerun-io/rerun/issues/3728 - if spatial_ctx.space_view_class_identifier == SpatialSpaceView2D::identifier() { - self.data.add_bounding_box( - entity_path.hash(), - bounding_box_for_textured_rect(&textured_rect), - spatial_ctx.world_from_entity, - ); - } - self.images.push(PickableImageRect { ent_path: entity_path.clone(), row_id: tensor_data_row_id, diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/lines2d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/lines2d.rs index 0a261f91a32c..fa33b888ded1 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/lines2d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/lines2d.rs @@ -69,10 +69,14 @@ impl Lines2DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Lines2D"); + let mut line_batch = line_builder .batch(entity_path.to_string()) .depth_offset(ent_context.depth_offset) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -99,11 +103,8 @@ impl Lines2DVisualizer { } } - self.data.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.data + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { // If there's many strips but only a single label, place the single label at the middle of the visualization. @@ -131,7 +132,7 @@ impl Lines2DVisualizer { data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs index c533b72f79cf..4ace87adeb25 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs @@ -71,10 +71,14 @@ impl Lines3DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Lines2D"); + let mut line_batch = line_builder .batch(entity_path.to_string()) .depth_offset(ent_context.depth_offset) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -106,11 +110,8 @@ impl Lines3DVisualizer { } debug_assert_eq!(data.strips.len(), num_rendered_strips, "the number of renderer strips after all post-processing is done should be equal to {} (got {num_rendered_strips} instead)", data.strips.len()); - self.data.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.data + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { // If there's many strips but only a single label, place the single label at the middle of the visualization. @@ -136,7 +137,7 @@ impl Lines3DVisualizer { data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs b/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs index 39036ec21cd7..defea630efed 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs @@ -119,26 +119,27 @@ impl Mesh3DVisualizer { }); if let Some(mesh) = mesh { - instances.extend(mesh.mesh_instances.iter().map(move |mesh_instance| { - let entity_from_mesh = mesh_instance.world_from_mesh; - let world_from_mesh = ent_context.world_from_entity * entity_from_mesh; - - MeshInstance { - gpu_mesh: mesh_instance.gpu_mesh.clone(), - world_from_mesh, - outline_mask_ids, - picking_layer_id: picking_layer_id_from_instance_path_hash( - picking_instance_hash, - ), - ..Default::default() - } - })); - - self.0.add_bounding_box( - entity_path.hash(), - mesh.bbox(), - ent_context.world_from_entity, - ); + // Let's draw the mesh once for every instance transform. + // TODO(#7026): This a rare form of hybrid joining. + for &world_from_instance in &ent_context.transform_info.reference_from_instances { + instances.extend(mesh.mesh_instances.iter().map(move |mesh_instance| { + let entity_from_mesh = mesh_instance.world_from_mesh; + let world_from_mesh = world_from_instance * entity_from_mesh; + + MeshInstance { + gpu_mesh: mesh_instance.gpu_mesh.clone(), + world_from_mesh, + outline_mask_ids, + picking_layer_id: picking_layer_id_from_instance_path_hash( + picking_instance_hash, + ), + ..Default::default() + } + })); + + self.0 + .add_bounding_box(entity_path.hash(), mesh.bbox(), world_from_instance); + } }; } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs index f805c5f6aa65..20db4b099931 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs @@ -26,9 +26,9 @@ pub use images::ImageVisualizer; pub use segmentation_images::SegmentationImageVisualizer; pub use transform3d_arrows::{add_axis_arrows, AxisLengthDetector, Transform3DArrowsVisualizer}; pub use utilities::{ - bounding_box_for_textured_rect, entity_iterator, process_labels_2d, process_labels_3d, - process_labels_3d_2, textured_rect_from_image, textured_rect_from_tensor, - SpatialViewVisualizerData, UiLabel, UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, + entity_iterator, process_labels_2d, process_labels_3d, process_labels_3d_2, + textured_rect_from_image, textured_rect_from_tensor, SpatialViewVisualizerData, UiLabel, + UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, }; // --- @@ -320,9 +320,12 @@ pub fn load_keypoint_connections( line_builder.reserve_strips(max_num_connections)?; line_builder.reserve_vertices(max_num_connections * 2)?; + // The calling visualizer has the same issue of not knowing what do with per instance transforms + // and should have warned already if there are multiple transforms. + let world_from_obj = ent_context.transform_info.single_entity_transform_silent(); let mut line_batch = line_builder .batch("keypoint connections") - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .picking_object_id(re_renderer::PickingLayerObjectId(ent_path.hash64())); // TODO(andreas): Make configurable. Should we pick up the point's radius and make this proportional? diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs index ea6fd138026a..5011f5fbb828 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs @@ -84,6 +84,9 @@ impl Points2DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Points2D"); { let point_batch = point_builder .batch(entity_path.to_string()) @@ -92,7 +95,7 @@ impl Points2DVisualizer { re_renderer::renderer::PointCloudBatchFlags::FLAG_DRAW_AS_CIRCLES | re_renderer::renderer::PointCloudBatchFlags::FLAG_ENABLE_SHADING, ) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -120,11 +123,8 @@ impl Points2DVisualizer { let obj_space_bounding_box = re_math::BoundingBox::from_points(positions.iter().copied()); - self.data.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.data + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); load_keypoint_connections(line_builder, ent_context, entity_path, &keypoints)?; @@ -147,7 +147,7 @@ impl Points2DVisualizer { &data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs index 04db96274ec8..117c239474e4 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs @@ -95,10 +95,14 @@ impl Points3DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let world_from_obj = ent_context + .transform_info + .single_entity_transform_required(entity_path, "Points3D"); + { let point_batch = point_builder .batch(entity_path.to_string()) - .world_from_obj(ent_context.world_from_entity) + .world_from_obj(world_from_obj) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); @@ -125,11 +129,8 @@ impl Points3DVisualizer { let obj_space_bounding_box = re_math::BoundingBox::from_points(positions.iter().copied()); - self.data.add_bounding_box( - entity_path.hash(), - obj_space_bounding_box, - ent_context.world_from_entity, - ); + self.data + .add_bounding_box(entity_path.hash(), obj_space_bounding_box, world_from_obj); load_keypoint_connections(line_builder, ent_context, entity_path, &keypoints)?; @@ -150,7 +151,7 @@ impl Points3DVisualizer { &data.labels, &colors, &annotation_infos, - ent_context.world_from_entity, + world_from_obj, )); } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs b/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs index e1d571116c81..3354a55f66b8 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/segmentation_images.rs @@ -6,7 +6,7 @@ use re_types::{ tensor_data::TensorDataMeaning, }; use re_viewer_context::{ - ApplicableEntities, IdentifiedViewSystem, ImageInfo, QueryContext, SpaceViewClass, + ApplicableEntities, IdentifiedViewSystem, ImageInfo, QueryContext, SpaceViewSystemExecutionError, TypedComponentFallbackProvider, ViewContext, ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, VisualizerQueryInfo, VisualizerSystem, @@ -16,10 +16,10 @@ use crate::{ ui::SpatialSpaceViewState, view_kind::SpatialSpaceViewKind, visualizers::{filter_visualizable_2d_entities, textured_rect_from_image}, - PickableImageRect, SpatialSpaceView2D, + PickableImageRect, }; -use super::{bounding_box_for_textured_rect, SpatialViewVisualizerData}; +use super::SpatialViewVisualizerData; pub struct SegmentationImageVisualizer { pub data: SpatialViewVisualizerData, @@ -137,21 +137,9 @@ impl VisualizerSystem for SegmentationImageVisualizer { &image, meaning, multiplicative_tint, + "SegmentationImage", + &mut self.data, ) { - // Only update the bounding box if this is a 2D space view. - // This is avoids a cyclic relationship where the image plane grows - // the bounds which in turn influence the size of the image plane. - // See: https://github.com/rerun-io/rerun/issues/3728 - if spatial_ctx.space_view_class_identifier - == SpatialSpaceView2D::identifier() - { - self.data.add_bounding_box( - entity_path.hash(), - bounding_box_for_textured_rect(&textured_rect), - spatial_ctx.world_from_entity, - ); - } - self.images.push(PickableImageRect { ent_path: entity_path.clone(), row_id: image.blob_row_id, diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs b/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs index e25cc399ec18..ee97433c8da0 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/transform3d_arrows.rs @@ -70,13 +70,27 @@ impl VisualizerSystem for Transform3DArrowsVisualizer { for data_result in query.iter_visible_data_results(ctx, Self::identifier()) { // Use transform without potential pinhole, since we don't want to visualize image-space coordinates. - let Some(world_from_obj) = transforms.reference_from_entity_ignoring_pinhole( - &data_result.entity_path, - ctx.recording(), - &latest_at_query, - ) else { + let Some(transform_info) = + transforms.transform_info_for_entity(&data_result.entity_path) + else { continue; }; + let world_from_obj = if let Some(twod_in_threed_info) = + &transform_info.twod_in_threed_info + { + if twod_in_threed_info.parent_pinhole != data_result.entity_path { + // We're inside a 2D space. But this is a 3D transform. + // Something is wrong here and this is not the right place to report it. + // Better just don't draw the axis! + continue; + } else { + // Don't apply the from-2D transform, stick with the last known 3D. + twod_in_threed_info.reference_from_pinhole_entity + } + } else { + transform_info + .single_entity_transform_required(&data_result.entity_path, "Transform3DArrows") + }; let results = data_result .latest_at_with_blueprint_resolved_data::(ctx, &latest_at_query); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs index ec22f0dfb510..2daf54957879 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs @@ -8,15 +8,12 @@ use re_space_view::{ }; use re_types::Archetype; use re_viewer_context::{ - IdentifiedViewSystem, QueryContext, QueryRange, SpaceViewClass, SpaceViewSystemExecutionError, - ViewContext, ViewContextCollection, ViewQuery, + IdentifiedViewSystem, QueryContext, QueryRange, SpaceViewSystemExecutionError, ViewContext, + ViewContextCollection, ViewQuery, }; -use crate::{ - contexts::{ - AnnotationSceneContext, EntityDepthOffsets, SpatialSceneEntityContext, TransformContext, - }, - SpatialSpaceView3D, +use crate::contexts::{ + AnnotationSceneContext, EntityDepthOffsets, SpatialSceneEntityContext, TransformContext, }; // --- @@ -162,24 +159,14 @@ where let system_identifier = System::identifier(); for data_result in query.iter_visible_data_results(ctx, system_identifier) { - // The transform that considers pinholes only makes sense if this is a 3D space-view - let world_from_entity = - if view_ctx.space_view_class_identifier() == SpatialSpaceView3D::identifier() { - transforms.reference_from_entity(&data_result.entity_path) - } else { - transforms.reference_from_entity_ignoring_pinhole( - &data_result.entity_path, - ctx.recording(), - &latest_at, - ) - }; - - let Some(world_from_entity) = world_from_entity else { + let Some(transform_info) = transforms.transform_info_for_entity(&data_result.entity_path) + else { continue; }; + let depth_offset_key = (system_identifier, data_result.entity_path.hash()); let entity_context = SpatialSceneEntityContext { - world_from_entity, + transform_info, depth_offset: depth_offsets .per_entity_and_visualizer .get(&depth_offset_key) @@ -282,24 +269,13 @@ where let system_identifier = System::identifier(); for data_result in query.iter_visible_data_results(ctx, system_identifier) { - // The transform that considers pinholes only makes sense if this is a 3D space-view - let world_from_entity = - if view_ctx.space_view_class_identifier() == SpatialSpaceView3D::identifier() { - transforms.reference_from_entity(&data_result.entity_path) - } else { - transforms.reference_from_entity_ignoring_pinhole( - &data_result.entity_path, - ctx.recording(), - &latest_at, - ) - }; - - let Some(world_from_entity) = world_from_entity else { + let Some(transform_info) = transforms.transform_info_for_entity(&data_result.entity_path) + else { continue; }; let depth_offset_key = (system_identifier, data_result.entity_path.hash()); let entity_context = SpatialSceneEntityContext { - world_from_entity, + transform_info, depth_offset: depth_offsets .per_entity_and_visualizer .get(&depth_offset_key) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs index b450dc6eff64..fb233e396331 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs @@ -8,6 +8,4 @@ pub use labels::{ UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, }; pub use spatial_view_visualizer::SpatialViewVisualizerData; -pub use textured_rect::{ - bounding_box_for_textured_rect, textured_rect_from_image, textured_rect_from_tensor, -}; +pub use textured_rect::{textured_rect_from_image, textured_rect_from_tensor}; diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/textured_rect.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/textured_rect.rs index ac591e9a69d3..9845842b0a0f 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/textured_rect.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/textured_rect.rs @@ -3,9 +3,13 @@ use re_chunk_store::RowId; use re_log_types::EntityPath; use re_renderer::renderer; use re_types::{components::Colormap, datatypes::TensorData, tensor_data::TensorDataMeaning}; -use re_viewer_context::{gpu_bridge, ImageInfo, ImageStatsCache, TensorStatsCache, ViewerContext}; +use re_viewer_context::{ + gpu_bridge, ImageInfo, ImageStatsCache, SpaceViewClass as _, TensorStatsCache, ViewerContext, +}; -use crate::contexts::SpatialSceneEntityContext; +use crate::{contexts::SpatialSceneEntityContext, SpatialSpaceView2D}; + +use super::SpatialViewVisualizerData; #[allow(clippy::too_many_arguments)] pub fn textured_rect_from_image( @@ -15,6 +19,8 @@ pub fn textured_rect_from_image( image: &ImageInfo, meaning: TensorDataMeaning, multiplicative_tint: egui::Rgba, + visualizer_name: &'static str, + visualizer_data: &mut SpatialViewVisualizerData, ) -> Option { let render_ctx = ctx.render_ctx?; @@ -47,9 +53,11 @@ pub fn textured_rect_from_image( renderer::TextureFilterMin::Linear }; - let world_from_entity = ent_context.world_from_entity; + let world_from_entity = ent_context + .transform_info + .single_entity_transform_required(ent_path, visualizer_name); - Some(renderer::TexturedRect { + let textured_rect = renderer::TexturedRect { top_left_corner_position: world_from_entity.transform_point3(Vec3::ZERO), extent_u: world_from_entity.transform_vector3(Vec3::X * image.width() as f32), extent_v: world_from_entity.transform_vector3(Vec3::Y * image.height() as f32), @@ -63,7 +71,21 @@ pub fn textured_rect_from_image( depth_offset: ent_context.depth_offset, outline_mask: ent_context.highlight.overall, }, - }) + }; + + // Only update the bounding box if this is a 2D space view. + // This is avoids a cyclic relationship where the image plane grows + // the bounds which in turn influence the size of the image plane. + // See: https://github.com/rerun-io/rerun/issues/3728 + if ent_context.space_view_class_identifier == SpatialSpaceView2D::identifier() { + visualizer_data.add_bounding_box( + ent_path.hash(), + bounding_box_for_textured_rect(&textured_rect), + world_from_entity, + ); + } + + Some(textured_rect) } Err(err) => { @@ -83,6 +105,8 @@ pub fn textured_rect_from_tensor( meaning: TensorDataMeaning, multiplicative_tint: egui::Rgba, colormap: Option, + visualizer_name: &'static str, + visualizer_data: &mut SpatialViewVisualizerData, ) -> Option { let render_ctx = ctx.render_ctx?; @@ -121,9 +145,11 @@ pub fn textured_rect_from_tensor( renderer::TextureFilterMin::Linear }; - let world_from_entity = ent_context.world_from_entity; + let world_from_entity = ent_context + .transform_info + .single_entity_transform_required(ent_path, visualizer_name); - Some(renderer::TexturedRect { + let textured_rect = renderer::TexturedRect { top_left_corner_position: world_from_entity.transform_point3(Vec3::ZERO), extent_u: world_from_entity.transform_vector3(Vec3::X * width as f32), extent_v: world_from_entity.transform_vector3(Vec3::Y * height as f32), @@ -135,7 +161,21 @@ pub fn textured_rect_from_tensor( depth_offset: ent_context.depth_offset, outline_mask: ent_context.highlight.overall, }, - }) + }; + + // Only update the bounding box if this is a 2D space view. + // This is avoids a cyclic relationship where the image plane grows + // the bounds which in turn influence the size of the image plane. + // See: https://github.com/rerun-io/rerun/issues/3728 + if ent_context.space_view_class_identifier == SpatialSpaceView2D::identifier() { + visualizer_data.add_bounding_box( + ent_path.hash(), + bounding_box_for_textured_rect(&textured_rect), + world_from_entity, + ); + } + + Some(textured_rect) } Err(err) => { re_log::error_once!("Failed to create texture for {debug_name:?}: {err}"); @@ -144,9 +184,7 @@ pub fn textured_rect_from_tensor( } } -pub fn bounding_box_for_textured_rect( - textured_rect: &renderer::TexturedRect, -) -> re_math::BoundingBox { +fn bounding_box_for_textured_rect(textured_rect: &renderer::TexturedRect) -> re_math::BoundingBox { let left_top = textured_rect.top_left_corner_position; let extent_u = textured_rect.extent_u; let extent_v = textured_rect.extent_v; diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs index 546aedc943b5..7e3e463182fa 100644 --- a/crates/viewer/re_viewer/src/reflection/mod.rs +++ b/crates/viewer/re_viewer/src/reflection/mod.rs @@ -384,6 +384,41 @@ fn generate_component_reflection() -> Result::name(), + ComponentReflection { + docstring_md: "3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy.", + placeholder: Some(LeafRotationAxisAngle::default().to_arrow()?), + }, + ), + ( + ::name(), + ComponentReflection { + docstring_md: "A 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy.\n\nNote: although the x,y,z,w components of the quaternion will be passed through to the\ndatastore as provided, when used in the Viewer, quaternions will always be normalized.", + placeholder: Some(LeafRotationQuat::default().to_arrow()?), + }, + ), + ( + ::name(), + ComponentReflection { + docstring_md: "A 3D scale factor that doesn't propagate in the transform hierarchy.\n\nA scale of 1.0 means no scaling.\nA scale of 2.0 means doubling the size.\nEach component scales along the corresponding axis.", + placeholder: Some(LeafScale3D::default().to_arrow()?), + }, + ), + ( + ::name(), + ComponentReflection { + docstring_md: "A 3x3 transformation matrix Matrix that doesn't propagate in the transform hierarchy.\n\n3x3 matrixes are able to represent any affine transformation in 3D space,\ni.e. rotation, scaling, shearing, reflection etc.\n\nMatrices in Rerun are stored as flat list of coefficients in column-major order:\n```text\n column 0 column 1 column 2\n -------------------------------------------------\nrow 0 | flat_columns[0] flat_columns[3] flat_columns[6]\nrow 1 | flat_columns[1] flat_columns[4] flat_columns[7]\nrow 2 | flat_columns[2] flat_columns[5] flat_columns[8]\n```", + placeholder: Some(LeafTransformMat3x3::default().to_arrow()?), + }, + ), + ( + ::name(), + ComponentReflection { + docstring_md: "A translation vector in 3D space that doesn't propagate in the transform hierarchy.", + placeholder: Some(LeafTranslation3D::default().to_arrow()?), + }, + ), ( ::name(), ComponentReflection { @@ -440,13 +475,6 @@ fn generate_component_reflection() -> Result::name(), - ComponentReflection { - docstring_md: "An out-of-tree affine transform between two 3D spaces, represented in a given direction.\n\n\"Out-of-tree\" means that the transform only affects its own entity: children don't inherit from it.", - placeholder: Some(OutOfTreeTransform3D::default().to_arrow()?), - }, - ), ( ::name(), ComponentReflection { @@ -513,7 +541,7 @@ fn generate_component_reflection() -> Result::name(), ComponentReflection { - docstring_md: "A 3D rotation expressed as a quaternion.\n\nNote: although the x,y,z,w components of the quaternion will be passed through to the\ndatastore as provided, when used in the Viewer Quaternions will always be normalized.", + docstring_md: "A 3D rotation expressed as a quaternion.\n\nNote: although the x,y,z,w components of the quaternion will be passed through to the\ndatastore as provided, when used in the Viewer, quaternions will always be normalized.", placeholder: Some(RotationQuat::default().to_arrow()?), }, ), @@ -654,6 +682,30 @@ fn generate_component_reflection() -> Result ArchetypeReflectionMap { re_tracing::profile_function!(); let array = [ + ( + ArchetypeName::new("rerun.archetypes.LeafTransforms3D"), + ArchetypeReflection { + display_name: "Leaf transforms 3D", + docstring_md: "One or more transforms between the parent and the current entity which are *not* propagated in the transform hierarchy.\n\nFor transforms that are propagated in the transform hierarchy, see [`archetypes.Transform3D`](https://rerun.io/docs/reference/types/archetypes/transform3d).\n\nIf both [`archetypes.LeafTransforms3D`](https://rerun.io/docs/reference/types/archetypes/leaf_transforms3d) and [`archetypes.Transform3D`](https://rerun.io/docs/reference/types/archetypes/transform3d) are present,\nfirst the tree propagating [`archetypes.Transform3D`](https://rerun.io/docs/reference/types/archetypes/transform3d) is applied, then [`archetypes.LeafTransforms3D`](https://rerun.io/docs/reference/types/archetypes/leaf_transforms3d).\n\nCurrently, most visualizers support only a single leaf transform per entity.\nCheck archetype documentations for details - if not otherwise specified, only the first leaf transform is applied.\n\nFrom the point of view of the entity's coordinate system,\nall components are applied in the inverse order they are listed here.\nE.g. if both a translation and a max3x3 transform are present,\nthe 3x3 matrix is applied first, followed by the translation.\n\n## Example\n\n### Regular & leaf transform in tandom\n```ignore\nuse rerun::{\n demo_util::grid,\n external::{anyhow, glam},\n};\n\nfn main() -> anyhow::Result<()> {\n let rec =\n rerun::RecordingStreamBuilder::new(\"rerun_example_leaf_transform3d_combined\").spawn()?;\n\n rec.set_time_sequence(\"frame\", 0);\n\n // Log a box and points further down in the hierarchy.\n rec.log(\n \"world/box\",\n &rerun::Boxes3D::from_half_sizes([[1.0, 1.0, 1.0]]),\n )?;\n rec.log(\n \"world/box/points\",\n &rerun::Points3D::new(grid(glam::Vec3::splat(-10.0), glam::Vec3::splat(10.0), 10)),\n )?;\n\n for i in 0..180 {\n rec.set_time_sequence(\"frame\", i);\n\n // Log a regular transform which affects both the box and the points.\n rec.log(\n \"world/box\",\n &rerun::Transform3D::from_rotation(rerun::RotationAxisAngle {\n axis: [0.0, 0.0, 1.0].into(),\n angle: rerun::Angle::from_degrees(i as f32 * 2.0),\n }),\n )?;\n\n // Log an leaf transform which affects only the box.\n let translation = [0.0, 0.0, (i as f32 * 0.1 - 5.0).abs() - 5.0];\n rec.log(\n \"world/box\",\n &rerun::LeafTransforms3D::new().with_translations([translation]),\n )?;\n }\n\n Ok(())\n}\n```\n
\n\n \n \n \n \n \n\n
", + fields: vec![ + ArchetypeFieldReflection { component_name : + "rerun.components.LeafTranslation3D".into(), display_name : + "Translations", docstring_md : "Translation vectors.", }, + ArchetypeFieldReflection { component_name : + "rerun.components.LeafRotationAxisAngle".into(), display_name : + "Rotation axis angles", docstring_md : "Rotations via axis + angle.", + }, ArchetypeFieldReflection { component_name : + "rerun.components.LeafRotationQuat".into(), display_name : + "Quaternions", docstring_md : "Rotations via quaternion.", }, + ArchetypeFieldReflection { component_name : + "rerun.components.LeafScale3D".into(), display_name : "Scales", + docstring_md : "Scaling factors.", }, ArchetypeFieldReflection { + component_name : "rerun.components.LeafTransformMat3x3".into(), + display_name : "Mat 3x 3", docstring_md : + "3x3 transformation matrices.", }, + ], + }, + ), ( ArchetypeName::new("rerun.archetypes.Transform3D"), ArchetypeReflection { diff --git a/docs/content/reference/migration/migration-0-18.md b/docs/content/reference/migration/migration-0-18.md index 830732fbd581..eee404ea5e77 100644 --- a/docs/content/reference/migration/migration-0-18.md +++ b/docs/content/reference/migration/migration-0-18.md @@ -66,13 +66,12 @@ Other changes in data representation: * Angles (as used in `RotationAxisAngle`) are now always stored in radians, conversion functions for degrees are provided. Scaling no longer distinguishes uniform and 3D scaling in its data representation. Uniform scaling is now always expressed as 3 floats with the same value. -TODO(andreas): Write about LeafTransforms3D +`OutOfTreeTransform3D` got removed. Instead, there is now a new [`LeafTransforms3D`](https://rerun.io/docs/reference/types/archetypes/leaf_transform3d#speculative-link). archetype which fulfills the same role, but works more similar to the `Transform3D` archetype and is supported by all 3D spatial primitives. #### Python The `Transform3D` archetype no longer has a `transform` argument. Use one of the other arguments instead. -TODO(andreas): Not true as of writing. but should be true at the time or release! Before: ```python @@ -83,13 +82,21 @@ After: rr.log("myentity", rr.Transform3D(translation=Vec3D([1, 2, 3]), relation=rr.TransformRelation.ChildFromParent)) ``` - -TODO(andreas): code example - - -TODO(andreas): Talk about LeafTransforms3D -TODO(andreas): … and Asset3D specifically - +Asset3D previously had a `transform` argument, now you have to log either a `LeafTransform3D` or a `Transform3D` on the same entity: +Before: +```python +rr.log("world/mesh", rr.Asset3D( + path=path, + transform=rr.OutOfTreeTransform3DBatch( + rr.TranslationRotationScale3D(translation=center, scale=scale) + ) + )) +``` +After: +```python +rr.log("world/mesh", rr.Asset3D(path=path)) +rr.log("world/mesh", rr.LeafTransform3D(translation=center, scale=scale)) +``` #### C++ @@ -119,18 +126,28 @@ an empty archetype instead that you can populate (e.g. `rerun::Transform3D().wit Scale is no longer an enum datatype but a component with a 3D vec: Before: -```rust -let scale_uniform = rerun::Scale3D::Uniform(2.0); -let scale_y = rerun::Scale3D::ThreeD([1.0, 2.0, 1.0]); +```cpp +auto scale_uniform = rerun::Scale3D::Uniform(2.0); +auto scale_y = rerun::Scale3D::ThreeD([1.0, 2.0, 1.0]); ``` After: -```rust -let scale_uniform = rerun::Scale3D::uniform(2.0); -let scale_y = rerun::Scale3D::from([1.0, 2.0, 1.0]); +```cpp +auto scale_uniform = rerun::Scale3D::uniform(2.0); +auto scale_y = rerun::Scale3D::from([1.0, 2.0, 1.0]); ``` -TODO(andreas): Talk about LeafTransforms3D -TODO(andreas): … and Asset3D specifically +Asset3D previously had a `transform` field, now you have to log either a `LeafTransform3D` or a `Transform3D` on the same entity: +Before: +```cpp +rec.log("world/asset", rerun::Asset3D::from_file(path).value_or_throw() + .with_transform(rerun::OutOfTreeTransform3D(translation)) +); +``` +After: +```cpp +rec.log("world/asset", rerun::Asset3D::from_file(path).value_or_throw()); +rec.log("world/mesh", &rerun::archetypes::LeafTransform3D().with_translations(translation)); +``` #### Rust `rerun::archetypes::Transform3D` no longer has a `new`, use other factory methods instead, e.g. `from_translation_rotation_scale` or `from_mat3x3` @@ -164,13 +181,12 @@ impl From for rerun::Transform3D { fn from(transform: GltfTransform) -> Self { rerun::Transform3D::from_translation_rotation_scale( transform.t, - rerun::datatypes::Quaternion::from_xyzw(transform.r), + rerun::Quaternion::from_xyzw(transform.r), transform.s, ) } } ``` -TODO(andreas): Quaternion in above snippet is likely to change as well. Since all aspects of the transform archetypes are now granular, they can be chained with `with_` functions: ```rust @@ -181,5 +197,16 @@ Note that the order of the method calls does _not_ affect the order in which tra `rerun::Transform3D::IDENTITY` has been removed, sue `rerun::Transform3D::default()` to start out with an empty archetype instead that you can populate (e.g. `rerun::Transform3D::default().with_mat3x3(rerun::datatypes::Mat3x3::IDENTITY)`). -TODO(andreas): Talk about LeafTransforms3D -TODO(andreas): … and Asset3D specifically + +Asset3D previously had a `transform` field, now you have to log either a `LeafTransform3D` or a `Transform3D` on the same entity: +Before: +```rust +rec.log("world/mesh", &rerun::Asset3D::from_file(path)? + .with_transform(rerun::OutOfTreeTransform3D::from(rerun::TranslationRotationScale3D(translation))) +)?; +``` +After: +```rust +rec.log("world/mesh", &rerun::Asset3D::from_file(path)?)?; +rec.log("world/mesh", &rerun::LeafTransform3D::default().with_translations([translation]))?; +``` diff --git a/docs/content/reference/types/archetypes.md b/docs/content/reference/types/archetypes.md index 916c9205a652..0ceb8ac66911 100644 --- a/docs/content/reference/types/archetypes.md +++ b/docs/content/reference/types/archetypes.md @@ -40,6 +40,7 @@ This page lists all built-in archetypes. * [`Asset3D`](archetypes/asset3d.md): A prepacked 3D asset (`.gltf`, `.glb`, `.obj`, `.stl`, etc.). * [`Boxes3D`](archetypes/boxes3d.md): 3D boxes with half-extents and optional center, rotations, colors etc. * [`Ellipsoids`](archetypes/ellipsoids.md): 3D ellipsoids or spheres. +* [`LeafTransforms3D`](archetypes/leaf_transforms3d.md): One or more transforms between the parent and the current entity which are *not* propagated in the transform hierarchy. * [`LineStrips3D`](archetypes/line_strips3d.md): 3D line strips with positions and optional colors, radii, labels, etc. * [`Mesh3D`](archetypes/mesh3d.md): A 3D triangle mesh as specified by its per-mesh and per-vertex properties. * [`Pinhole`](archetypes/pinhole.md): Camera perspective projection (a.k.a. intrinsics). diff --git a/docs/content/reference/types/archetypes/.gitattributes b/docs/content/reference/types/archetypes/.gitattributes index a47310c9498b..de7c9ad2068a 100644 --- a/docs/content/reference/types/archetypes/.gitattributes +++ b/docs/content/reference/types/archetypes/.gitattributes @@ -14,6 +14,7 @@ disconnected_space.md linguist-generated=true ellipsoids.md linguist-generated=true image.md linguist-generated=true image_encoded.md linguist-generated=true +leaf_transforms3d.md linguist-generated=true line_strips2d.md linguist-generated=true line_strips3d.md linguist-generated=true mesh3d.md linguist-generated=true diff --git a/docs/content/reference/types/archetypes/asset3d.md b/docs/content/reference/types/archetypes/asset3d.md index 880727f1cfd1..17a991d3290e 100644 --- a/docs/content/reference/types/archetypes/asset3d.md +++ b/docs/content/reference/types/archetypes/asset3d.md @@ -7,14 +7,15 @@ A prepacked 3D asset (`.gltf`, `.glb`, `.obj`, `.stl`, etc.). See also [`archetypes.Mesh3D`](https://rerun.io/docs/reference/types/archetypes/mesh3d). +If there are multiple [`archetypes.LeafTransforms3D`](https://rerun.io/docs/reference/types/archetypes/leaf_transforms3d) instances logged to the same entity as a mesh, +an instance of the mesh will be drawn for each transform. + ## Components **Required**: [`Blob`](../components/blob.md) **Recommended**: [`MediaType`](../components/media_type.md) -**Optional**: [`OutOfTreeTransform3D`](../components/out_of_tree_transform3d.md) - ## Shown in * [Spatial3DView](../views/spatial3d_view.md) * [Spatial2DView](../views/spatial2d_view.md) (if logged above active projection) @@ -24,7 +25,7 @@ See also [`archetypes.Mesh3D`](https://rerun.io/docs/reference/types/archetypes/ * 🐍 [Python API docs for `Asset3D`](https://ref.rerun.io/docs/python/stable/common/archetypes#rerun.archetypes.Asset3D) * 🦀 [Rust API docs for `Asset3D`](https://docs.rs/rerun/latest/rerun/archetypes/struct.Asset3D.html) -## Examples +## Example ### Simple 3D asset @@ -38,7 +39,3 @@ snippet: archetypes/asset3d_simple -### 3D asset with out-of-tree transform - -snippet: archetypes/asset3d_out_of_tree - diff --git a/docs/content/reference/types/archetypes/leaf_transforms3d.md b/docs/content/reference/types/archetypes/leaf_transforms3d.md new file mode 100644 index 000000000000..6bd8c93acd7e --- /dev/null +++ b/docs/content/reference/types/archetypes/leaf_transforms3d.md @@ -0,0 +1,47 @@ +--- +title: "LeafTransforms3D" +--- + + +One or more transforms between the parent and the current entity which are *not* propagated in the transform hierarchy. + +For transforms that are propagated in the transform hierarchy, see [`archetypes.Transform3D`](https://rerun.io/docs/reference/types/archetypes/transform3d). + +If both [`archetypes.LeafTransforms3D`](https://rerun.io/docs/reference/types/archetypes/leaf_transforms3d) and [`archetypes.Transform3D`](https://rerun.io/docs/reference/types/archetypes/transform3d) are present, +first the tree propagating [`archetypes.Transform3D`](https://rerun.io/docs/reference/types/archetypes/transform3d) is applied, then [`archetypes.LeafTransforms3D`](https://rerun.io/docs/reference/types/archetypes/leaf_transforms3d). + +Currently, most visualizers support only a single leaf transform per entity. +Check archetype documentations for details - if not otherwise specified, only the first leaf transform is applied. + +From the point of view of the entity's coordinate system, +all components are applied in the inverse order they are listed here. +E.g. if both a translation and a max3x3 transform are present, +the 3x3 matrix is applied first, followed by the translation. + +## Components + +**Optional**: [`LeafTranslation3D`](../components/leaf_translation3d.md), [`LeafRotationAxisAngle`](../components/leaf_rotation_axis_angle.md), [`LeafRotationQuat`](../components/leaf_rotation_quat.md), [`LeafScale3D`](../components/leaf_scale3d.md), [`LeafTransformMat3x3`](../components/leaf_transform_mat3x3.md) + +## Shown in +* [Spatial3DView](../views/spatial3d_view.md) +* [Spatial2DView](../views/spatial2d_view.md) (if logged above active projection) + +## API reference links + * 🌊 [C++ API docs for `LeafTransforms3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1archetypes_1_1LeafTransforms3D.html) + * 🐍 [Python API docs for `LeafTransforms3D`](https://ref.rerun.io/docs/python/stable/common/archetypes#rerun.archetypes.LeafTransforms3D) + * 🦀 [Rust API docs for `LeafTransforms3D`](https://docs.rs/rerun/latest/rerun/archetypes/struct.LeafTransforms3D.html) + +## Example + +### Regular & leaf transform in tandom + +snippet: archetypes/leaf_transforms3d_combined + + + + + + + + + diff --git a/docs/content/reference/types/archetypes/mesh3d.md b/docs/content/reference/types/archetypes/mesh3d.md index 37461ed89904..e9dace74ced1 100644 --- a/docs/content/reference/types/archetypes/mesh3d.md +++ b/docs/content/reference/types/archetypes/mesh3d.md @@ -7,6 +7,9 @@ A 3D triangle mesh as specified by its per-mesh and per-vertex properties. See also [`archetypes.Asset3D`](https://rerun.io/docs/reference/types/archetypes/asset3d). +If there are multiple [`archetypes.LeafTransforms3D`](https://rerun.io/docs/reference/types/archetypes/leaf_transforms3d) instances logged to the same entity as a mesh, +an instance of the mesh will be drawn for each transform. + ## Components **Required**: [`Position3D`](../components/position3d.md) @@ -50,3 +53,15 @@ snippet: archetypes/mesh3d_partial_updates +### 3D mesh with leaf transforms + +snippet: archetypes/mesh3d_leaf_transforms3d + + + + + + + + + diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index c7e5090e2211..3a2e31e2e8b0 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -34,6 +34,11 @@ on [Entities and Components](../../concepts/entity-component.md). * [`HalfSize3D`](components/half_size3d.md): Half-size (radius) of a 3D box. * [`ImagePlaneDistance`](components/image_plane_distance.md): The distance from the camera origin to the image plane when the projection is shown in a 3D viewer. * [`KeypointId`](components/keypoint_id.md): A 16-bit ID representing a type of semantic keypoint within a class. +* [`LeafRotationAxisAngle`](components/leaf_rotation_axis_angle.md): 3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy. +* [`LeafRotationQuat`](components/leaf_rotation_quat.md): A 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy. +* [`LeafScale3D`](components/leaf_scale3d.md): A 3D scale factor that doesn't propagate in the transform hierarchy. +* [`LeafTransformMat3x3`](components/leaf_transform_mat3x3.md): A 3x3 transformation matrix Matrix that doesn't propagate in the transform hierarchy. +* [`LeafTranslation3D`](components/leaf_translation3d.md): A translation vector in 3D space that doesn't propagate in the transform hierarchy. * [`LineStrip2D`](components/line_strip2d.md): A line strip in 2D space. * [`LineStrip3D`](components/line_strip3d.md): A line strip in 3D space. * [`MagnificationFilter`](components/magnification_filter.md): Filter used when magnifying an image/texture such that a single pixel/texel is displayed as multiple pixels on screen. @@ -42,7 +47,6 @@ on [Entities and Components](../../concepts/entity-component.md). * [`MediaType`](components/media_type.md): A standardized media type (RFC2046, formerly known as MIME types), encoded as a string. * [`Name`](components/name.md): A display name, typically for an entity or a item like a plot series. * [`Opacity`](components/opacity.md): Degree of transparency ranging from 0.0 (fully transparent) to 1.0 (fully opaque). -* [`OutOfTreeTransform3D`](components/out_of_tree_transform3d.md): An out-of-tree affine transform between two 3D spaces, represented in a given direction. * [`PinholeProjection`](components/pinhole_projection.md): Camera projection, from image coordinates to view coordinates. * [`Position2D`](components/position2d.md): A position in 2D space. * [`Position3D`](components/position3d.md): A position in 3D space. diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index a54341eb5dbf..b4fe052cf41f 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -22,6 +22,11 @@ half_size2d.md linguist-generated=true half_size3d.md linguist-generated=true image_plane_distance.md linguist-generated=true keypoint_id.md linguist-generated=true +leaf_rotation_axis_angle.md linguist-generated=true +leaf_rotation_quat.md linguist-generated=true +leaf_scale3d.md linguist-generated=true +leaf_transform_mat3x3.md linguist-generated=true +leaf_translation3d.md linguist-generated=true line_strip2d.md linguist-generated=true line_strip3d.md linguist-generated=true magnification_filter.md linguist-generated=true @@ -30,7 +35,6 @@ marker_size.md linguist-generated=true media_type.md linguist-generated=true name.md linguist-generated=true opacity.md linguist-generated=true -out_of_tree_transform3d.md linguist-generated=true pinhole_projection.md linguist-generated=true position2d.md linguist-generated=true position3d.md linguist-generated=true diff --git a/docs/content/reference/types/components/leaf_rotation_axis_angle.md b/docs/content/reference/types/components/leaf_rotation_axis_angle.md new file mode 100644 index 000000000000..887479537466 --- /dev/null +++ b/docs/content/reference/types/components/leaf_rotation_axis_angle.md @@ -0,0 +1,20 @@ +--- +title: "LeafRotationAxisAngle" +--- + + +3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy. + +## Fields + +* rotation: [`RotationAxisAngle`](../datatypes/rotation_axis_angle.md) + +## API reference links + * 🌊 [C++ API docs for `LeafRotationAxisAngle`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1LeafRotationAxisAngle.html?speculative-link) + * 🐍 [Python API docs for `LeafRotationAxisAngle`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.LeafRotationAxisAngle) + * 🦀 [Rust API docs for `LeafRotationAxisAngle`](https://docs.rs/rerun/latest/rerun/components/struct.LeafRotationAxisAngle.html?speculative-link) + + +## Used by + +* [`LeafTransforms3D`](../archetypes/leaf_transforms3d.md) diff --git a/docs/content/reference/types/components/leaf_rotation_quat.md b/docs/content/reference/types/components/leaf_rotation_quat.md new file mode 100644 index 000000000000..f514ed44518c --- /dev/null +++ b/docs/content/reference/types/components/leaf_rotation_quat.md @@ -0,0 +1,23 @@ +--- +title: "LeafRotationQuat" +--- + + +A 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy. + +Note: although the x,y,z,w components of the quaternion will be passed through to the +datastore as provided, when used in the Viewer, quaternions will always be normalized. + +## Fields + +* quaternion: [`Quaternion`](../datatypes/quaternion.md) + +## API reference links + * 🌊 [C++ API docs for `LeafRotationQuat`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1LeafRotationQuat.html?speculative-link) + * 🐍 [Python API docs for `LeafRotationQuat`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.LeafRotationQuat) + * 🦀 [Rust API docs for `LeafRotationQuat`](https://docs.rs/rerun/latest/rerun/components/struct.LeafRotationQuat.html?speculative-link) + + +## Used by + +* [`LeafTransforms3D`](../archetypes/leaf_transforms3d.md) diff --git a/docs/content/reference/types/components/leaf_scale3d.md b/docs/content/reference/types/components/leaf_scale3d.md new file mode 100644 index 000000000000..1e72a5ad0d92 --- /dev/null +++ b/docs/content/reference/types/components/leaf_scale3d.md @@ -0,0 +1,24 @@ +--- +title: "LeafScale3D" +--- + + +A 3D scale factor that doesn't propagate in the transform hierarchy. + +A scale of 1.0 means no scaling. +A scale of 2.0 means doubling the size. +Each component scales along the corresponding axis. + +## Fields + +* scale: [`Vec3D`](../datatypes/vec3d.md) + +## API reference links + * 🌊 [C++ API docs for `LeafScale3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1LeafScale3D.html?speculative-link) + * 🐍 [Python API docs for `LeafScale3D`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.LeafScale3D) + * 🦀 [Rust API docs for `LeafScale3D`](https://docs.rs/rerun/latest/rerun/components/struct.LeafScale3D.html?speculative-link) + + +## Used by + +* [`LeafTransforms3D`](../archetypes/leaf_transforms3d.md) diff --git a/docs/content/reference/types/components/leaf_transform_mat3x3.md b/docs/content/reference/types/components/leaf_transform_mat3x3.md new file mode 100644 index 000000000000..0fe2e9baf4eb --- /dev/null +++ b/docs/content/reference/types/components/leaf_transform_mat3x3.md @@ -0,0 +1,32 @@ +--- +title: "LeafTransformMat3x3" +--- + + +A 3x3 transformation matrix Matrix that doesn't propagate in the transform hierarchy. + +3x3 matrixes are able to represent any affine transformation in 3D space, +i.e. rotation, scaling, shearing, reflection etc. + +Matrices in Rerun are stored as flat list of coefficients in column-major order: +```text + column 0 column 1 column 2 + ------------------------------------------------- +row 0 | flat_columns[0] flat_columns[3] flat_columns[6] +row 1 | flat_columns[1] flat_columns[4] flat_columns[7] +row 2 | flat_columns[2] flat_columns[5] flat_columns[8] +``` + +## Fields + +* matrix: [`Mat3x3`](../datatypes/mat3x3.md) + +## API reference links + * 🌊 [C++ API docs for `LeafTransformMat3x3`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1LeafTransformMat3x3.html?speculative-link) + * 🐍 [Python API docs for `LeafTransformMat3x3`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.LeafTransformMat3x3) + * 🦀 [Rust API docs for `LeafTransformMat3x3`](https://docs.rs/rerun/latest/rerun/components/struct.LeafTransformMat3x3.html?speculative-link) + + +## Used by + +* [`LeafTransforms3D`](../archetypes/leaf_transforms3d.md) diff --git a/docs/content/reference/types/components/leaf_translation3d.md b/docs/content/reference/types/components/leaf_translation3d.md new file mode 100644 index 000000000000..83896d17052f --- /dev/null +++ b/docs/content/reference/types/components/leaf_translation3d.md @@ -0,0 +1,20 @@ +--- +title: "LeafTranslation3D" +--- + + +A translation vector in 3D space that doesn't propagate in the transform hierarchy. + +## Fields + +* vector: [`Vec3D`](../datatypes/vec3d.md) + +## API reference links + * 🌊 [C++ API docs for `LeafTranslation3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1LeafTranslation3D.html?speculative-link) + * 🐍 [Python API docs for `LeafTranslation3D`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.LeafTranslation3D) + * 🦀 [Rust API docs for `LeafTranslation3D`](https://docs.rs/rerun/latest/rerun/components/struct.LeafTranslation3D.html?speculative-link) + + +## Used by + +* [`LeafTransforms3D`](../archetypes/leaf_transforms3d.md) diff --git a/docs/content/reference/types/components/out_of_tree_transform3d.md b/docs/content/reference/types/components/out_of_tree_transform3d.md deleted file mode 100644 index ca8ec12b7174..000000000000 --- a/docs/content/reference/types/components/out_of_tree_transform3d.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: "OutOfTreeTransform3D" ---- - - -An out-of-tree affine transform between two 3D spaces, represented in a given direction. - -"Out-of-tree" means that the transform only affects its own entity: children don't inherit from it. - -## Fields - -* repr: [`Transform3D`](../datatypes/transform3d.md) - -## API reference links - * 🌊 [C++ API docs for `OutOfTreeTransform3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1OutOfTreeTransform3D.html) - * 🐍 [Python API docs for `OutOfTreeTransform3D`](https://ref.rerun.io/docs/python/stable/common/components#rerun.components.OutOfTreeTransform3D) - * 🦀 [Rust API docs for `OutOfTreeTransform3D`](https://docs.rs/rerun/latest/rerun/components/struct.OutOfTreeTransform3D.html) - - -## Used by - -* [`Asset3D`](../archetypes/asset3d.md) diff --git a/docs/content/reference/types/components/rotation_quat.md b/docs/content/reference/types/components/rotation_quat.md index 1422195afdec..34960614834b 100644 --- a/docs/content/reference/types/components/rotation_quat.md +++ b/docs/content/reference/types/components/rotation_quat.md @@ -6,7 +6,7 @@ title: "RotationQuat" A 3D rotation expressed as a quaternion. Note: although the x,y,z,w components of the quaternion will be passed through to the -datastore as provided, when used in the Viewer Quaternions will always be normalized. +datastore as provided, when used in the Viewer, quaternions will always be normalized. ## Fields diff --git a/docs/content/reference/types/datatypes/mat3x3.md b/docs/content/reference/types/datatypes/mat3x3.md index cc9a9c416948..ea81160ddcdd 100644 --- a/docs/content/reference/types/datatypes/mat3x3.md +++ b/docs/content/reference/types/datatypes/mat3x3.md @@ -26,5 +26,6 @@ row 2 | flat_columns[2] flat_columns[5] flat_columns[8] ## Used by +* [`LeafTransformMat3x3`](../components/leaf_transform_mat3x3.md?speculative-link) * [`PinholeProjection`](../components/pinhole_projection.md) * [`TransformMat3x3`](../components/transform_mat3x3.md?speculative-link) diff --git a/docs/content/reference/types/datatypes/quaternion.md b/docs/content/reference/types/datatypes/quaternion.md index 2dee7995e377..d806ad25507d 100644 --- a/docs/content/reference/types/datatypes/quaternion.md +++ b/docs/content/reference/types/datatypes/quaternion.md @@ -20,5 +20,6 @@ datastore as provided, when used in the Viewer Quaternions will always be normal ## Used by +* [`LeafRotationQuat`](../components/leaf_rotation_quat.md?speculative-link) * [`Rotation3D`](../datatypes/rotation3d.md) * [`RotationQuat`](../components/rotation_quat.md?speculative-link) diff --git a/docs/content/reference/types/datatypes/rotation_axis_angle.md b/docs/content/reference/types/datatypes/rotation_axis_angle.md index d758088d2df4..df548704dd1e 100644 --- a/docs/content/reference/types/datatypes/rotation_axis_angle.md +++ b/docs/content/reference/types/datatypes/rotation_axis_angle.md @@ -18,5 +18,6 @@ title: "RotationAxisAngle" ## Used by +* [`LeafRotationAxisAngle`](../components/leaf_rotation_axis_angle.md?speculative-link) * [`Rotation3D`](../datatypes/rotation3d.md) * [`RotationAxisAngle`](../components/rotation_axis_angle.md?speculative-link) diff --git a/docs/content/reference/types/datatypes/transform3d.md b/docs/content/reference/types/datatypes/transform3d.md index 970ccd641ce2..443132c49da3 100644 --- a/docs/content/reference/types/datatypes/transform3d.md +++ b/docs/content/reference/types/datatypes/transform3d.md @@ -17,5 +17,4 @@ Representation of a 3D affine transform. ## Used by -* [`OutOfTreeTransform3D`](../components/out_of_tree_transform3d.md) * [`Transform3D`](../components/transform3d.md) diff --git a/docs/content/reference/types/datatypes/vec3d.md b/docs/content/reference/types/datatypes/vec3d.md index ddd38d6f02f4..38a2abe96f46 100644 --- a/docs/content/reference/types/datatypes/vec3d.md +++ b/docs/content/reference/types/datatypes/vec3d.md @@ -18,6 +18,8 @@ A vector in 3D space. ## Used by * [`HalfSize3D`](../components/half_size3d.md) +* [`LeafScale3D`](../components/leaf_scale3d.md?speculative-link) +* [`LeafTranslation3D`](../components/leaf_translation3d.md?speculative-link) * [`LineStrip3D`](../components/line_strip3d.md) * [`Position3D`](../components/position3d.md) * [`RotationAxisAngle`](../datatypes/rotation_axis_angle.md) diff --git a/docs/content/reference/types/views/spatial2d_view.md b/docs/content/reference/types/views/spatial2d_view.md index dd65b44530c8..d51b649394e0 100644 --- a/docs/content/reference/types/views/spatial2d_view.md +++ b/docs/content/reference/types/views/spatial2d_view.md @@ -60,6 +60,7 @@ snippet: views/spatial2d * [`Asset3D`](../archetypes/asset3d.md) (if logged above active projection) * [`Boxes3D`](../archetypes/boxes3d.md) (if logged above active projection) * [`Ellipsoids`](../archetypes/ellipsoids.md) (if logged above active projection) +* [`LeafTransforms3D`](../archetypes/leaf_transforms3d.md) (if logged above active projection) * [`LineStrips3D`](../archetypes/line_strips3d.md) (if logged above active projection) * [`Mesh3D`](../archetypes/mesh3d.md) (if logged above active projection) * [`Points3D`](../archetypes/points3d.md) (if logged above active projection) diff --git a/docs/content/reference/types/views/spatial3d_view.md b/docs/content/reference/types/views/spatial3d_view.md index db564bdb56a3..2896d2ef47ae 100644 --- a/docs/content/reference/types/views/spatial3d_view.md +++ b/docs/content/reference/types/views/spatial3d_view.md @@ -45,6 +45,7 @@ snippet: views/spatial3d * [`Clear`](../archetypes/clear.md) * [`DisconnectedSpace`](../archetypes/disconnected_space.md) * [`Ellipsoids`](../archetypes/ellipsoids.md) +* [`LeafTransforms3D`](../archetypes/leaf_transforms3d.md) * [`LineStrips3D`](../archetypes/line_strips3d.md) * [`Mesh3D`](../archetypes/mesh3d.md) * [`Points3D`](../archetypes/points3d.md) diff --git a/docs/snippets/all/archetypes/asset3d_out_of_tree.cpp b/docs/snippets/all/archetypes/asset3d_out_of_tree.cpp deleted file mode 100644 index 4158d9364e8e..000000000000 --- a/docs/snippets/all/archetypes/asset3d_out_of_tree.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// Log a simple 3D asset with an out-of-tree transform which will not affect its children. - -#include -#include - -#include -#include - -int main(int argc, char** argv) { - if (argc < 2) { - throw std::runtime_error("Usage: "); - } - const auto path = argv[1]; - - const auto rec = rerun::RecordingStream("rerun_example_asset3d_out_of_tree"); - rec.spawn().exit_on_failure(); - - rec.log_static("world", rerun::ViewCoordinates::RIGHT_HAND_Z_UP); // Set an up-axis - - rec.set_time_sequence("frame", 0); - rec.log("world/asset", rerun::Asset3D::from_file(path).value_or_throw()); - // Those points will not be affected by their parent's out-of-tree transform! - rec.log( - "world/asset/points", - rerun::Points3D(rerun::demo::grid3d(-10.0f, 10.0f, 10)) - ); - - for (int64_t i = 1; i < 20; ++i) { - rec.set_time_sequence("frame", i); - - // Modify the asset's out-of-tree transform: this will not affect its children (i.e. the points)! - const auto translation = - rerun::TranslationRotationScale3D({0.0, 0.0, static_cast(i) - 10.0f}); - rec.log( - "world/asset", - rerun::Collection(rerun::OutOfTreeTransform3D(translation)) - ); - } -} diff --git a/docs/snippets/all/archetypes/asset3d_out_of_tree.py b/docs/snippets/all/archetypes/asset3d_out_of_tree.py deleted file mode 100644 index 35dd272dfcb6..000000000000 --- a/docs/snippets/all/archetypes/asset3d_out_of_tree.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Log a simple 3D asset with an out-of-tree transform which will not affect its children.""" - -import sys - -import numpy as np -import rerun as rr -from rerun.components import OutOfTreeTransform3DBatch -from rerun.datatypes import TranslationRotationScale3D - -if len(sys.argv) < 2: - print(f"Usage: {sys.argv[0]} ") - sys.exit(1) - -rr.init("rerun_example_asset3d_out_of_tree", spawn=True) - -rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Z_UP, static=True) # Set an up-axis - -rr.set_time_sequence("frame", 0) -rr.log("world/asset", rr.Asset3D(path=sys.argv[1])) -# Those points will not be affected by their parent's out-of-tree transform! -rr.log( - "world/asset/points", - rr.Points3D(np.vstack([xyz.ravel() for xyz in np.mgrid[3 * [slice(-10, 10, 10j)]]]).T), -) - -asset = rr.Asset3D(path=sys.argv[1]) -for i in range(1, 20): - rr.set_time_sequence("frame", i) - - translation = TranslationRotationScale3D(translation=[0, 0, i - 10.0]) - rr.log_components("world/asset", [OutOfTreeTransform3DBatch(translation)]) diff --git a/docs/snippets/all/archetypes/asset3d_out_of_tree.rs b/docs/snippets/all/archetypes/asset3d_out_of_tree.rs deleted file mode 100644 index 8673eeb4a73d..000000000000 --- a/docs/snippets/all/archetypes/asset3d_out_of_tree.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Log a simple 3D asset with an out-of-tree transform which will not affect its children. - -use rerun::{ - demo_util::grid, - external::{anyhow, glam}, -}; - -fn main() -> anyhow::Result<()> { - let args = std::env::args().collect::>(); - let Some(path) = args.get(1) else { - anyhow::bail!("Usage: {} ", args[0]); - }; - - let rec = rerun::RecordingStreamBuilder::new("rerun_example_asset3d_out_of_tree").spawn()?; - - rec.log_static("world", &rerun::ViewCoordinates::RIGHT_HAND_Z_UP)?; // Set an up-axis - - rec.set_time_sequence("frame", 0); - rec.log("world/asset", &rerun::Asset3D::from_file(path)?)?; - // Those points will not be affected by their parent's out-of-tree transform! - rec.log( - "world/asset/points", - &rerun::Points3D::new(grid(glam::Vec3::splat(-10.0), glam::Vec3::splat(10.0), 10)), - )?; - - for i in 1..20 { - rec.set_time_sequence("frame", i); - - // Modify the asset's out-of-tree transform: this will not affect its children (i.e. the points)! - let translation = - rerun::TranslationRotationScale3D::from_translation([0.0, 0.0, i as f32 - 10.0]); - rec.log_component_batches( - "world/asset", - false, - [&rerun::OutOfTreeTransform3D::from(translation) as _], - )?; - } - - Ok(()) -} diff --git a/docs/snippets/all/archetypes/leaf_transforms3d_combined.cpp b/docs/snippets/all/archetypes/leaf_transforms3d_combined.cpp new file mode 100644 index 000000000000..931d9d52c965 --- /dev/null +++ b/docs/snippets/all/archetypes/leaf_transforms3d_combined.cpp @@ -0,0 +1,36 @@ +// Log a simple 3D box with a regular & leaf transform. + +#include +#include + +int main() { + const auto rec = rerun::RecordingStream("rerun_example_leaf_transform3d_combined"); + rec.set_time_sequence("frame", 0); + + // Log a box and points further down in the hierarchy. + rec.log("world/box", rerun::Boxes3D::from_half_sizes({{1.0, 1.0, 1.0}})); + rec.log( + "world/box/points", + rerun::Points3D(rerun::demo::grid3d(-10.0f, 10.0f, 10)) + ); + + for (int i = 0; i < 180; ++i) { + rec.set_time_sequence("frame", i); + + // Log a regular transform which affects both the box and the points. + rec.log( + "world/box", + rerun::Transform3D::from_rotation(rerun::RotationAxisAngle{ + {0.0f, 0.0f, 1.0f}, + rerun::Angle::degrees(static_cast(i) * 2.0f)}) + ); + + // Log an leaf transform which affects only the box. + rec.log( + "world/box", + rerun::LeafTransforms3D().with_translations( + {{0.0f, 0.0f, std::abs(static_cast(i) * 0.1f - 5.0f) - 5.0f}} + ) + ); + } +} diff --git a/docs/snippets/all/archetypes/leaf_transforms3d_combined.py b/docs/snippets/all/archetypes/leaf_transforms3d_combined.py new file mode 100644 index 000000000000..0c5dcca64523 --- /dev/null +++ b/docs/snippets/all/archetypes/leaf_transforms3d_combined.py @@ -0,0 +1,21 @@ +"""Log a simple 3D box with a regular & leaf transform.""" + +import numpy as np +import rerun as rr + +rr.init("rerun_example_leaf_transform3d_combined", spawn=True) + +rr.set_time_sequence("frame", 0) + +# Log a box and points further down in the hierarchy. +rr.log("world/box", rr.Boxes3D(half_sizes=[[1.0, 1.0, 1.0]])) +rr.log("world/box/points", rr.Points3D(np.vstack([xyz.ravel() for xyz in np.mgrid[3 * [slice(-10, 10, 10j)]]]).T)) + +for i in range(0, 180): + rr.set_time_sequence("frame", i) + + # Log a regular transform which affects both the box and the points. + rr.log("world/box", rr.Transform3D(rotation_axis_angle=rr.RotationAxisAngle([0, 0, 1], angle=rr.Angle(deg=i * 2)))) + + # Log an leaf transform which affects only the box. + rr.log("world/box", rr.LeafTransforms3D(translations=[0, 0, abs(i * 0.1 - 5.0) - 5.0])) diff --git a/docs/snippets/all/archetypes/leaf_transforms3d_combined.rs b/docs/snippets/all/archetypes/leaf_transforms3d_combined.rs new file mode 100644 index 000000000000..24b08cdffde4 --- /dev/null +++ b/docs/snippets/all/archetypes/leaf_transforms3d_combined.rs @@ -0,0 +1,45 @@ +//! Log a simple 3D box with a regular & leaf transform. + +use rerun::{ + demo_util::grid, + external::{anyhow, glam}, +}; + +fn main() -> anyhow::Result<()> { + let rec = + rerun::RecordingStreamBuilder::new("rerun_example_leaf_transform3d_combined").spawn()?; + + rec.set_time_sequence("frame", 0); + + // Log a box and points further down in the hierarchy. + rec.log( + "world/box", + &rerun::Boxes3D::from_half_sizes([[1.0, 1.0, 1.0]]), + )?; + rec.log( + "world/box/points", + &rerun::Points3D::new(grid(glam::Vec3::splat(-10.0), glam::Vec3::splat(10.0), 10)), + )?; + + for i in 0..180 { + rec.set_time_sequence("frame", i); + + // Log a regular transform which affects both the box and the points. + rec.log( + "world/box", + &rerun::Transform3D::from_rotation(rerun::RotationAxisAngle { + axis: [0.0, 0.0, 1.0].into(), + angle: rerun::Angle::from_degrees(i as f32 * 2.0), + }), + )?; + + // Log an leaf transform which affects only the box. + let translation = [0.0, 0.0, (i as f32 * 0.1 - 5.0).abs() - 5.0]; + rec.log( + "world/box", + &rerun::LeafTransforms3D::new().with_translations([translation]), + )?; + } + + Ok(()) +} diff --git a/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.cpp b/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.cpp new file mode 100644 index 000000000000..1c9751a1bed8 --- /dev/null +++ b/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.cpp @@ -0,0 +1,38 @@ +// Log a simple 3D mesh with several leaf-transforms which instantiate the mesh several times and will not affect its children. + +#include + +int main() { + const auto rec = rerun::RecordingStream("rerun_example_mesh3d_leaf_transforms3d"); + rec.spawn().exit_on_failure(); + + rec.set_time_sequence("frame", 0); + rec.log( + "shape", + rerun::Mesh3D( + {{1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f}, {-1.0f, 1.0f, -1.0f}, {1.0f, -1.0f, -1.0f}} + ) + .with_triangle_indices({{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3}}) + .with_vertex_colors({0xFF0000FF, 0x00FF00FF, 0x00000FFFF, 0xFFFF00FF}) + ); + // This box will not be affected by its parent's leaf transforms! + rec.log("shape/box", rerun::Boxes3D::from_half_sizes({{5.0f, 5.0f, 5.0f}})); + + for (int i = 0; i < 100; ++i) { + rec.set_time_sequence("frame", i); + rec.log( + "shape", + rerun::LeafTransforms3D() + .with_translations( + {{2.0f, 0.0f, 0.0f}, + {0.0f, 2.0f, 0.0f}, + {0.0f, -2.0f, 0.0f}, + {-2.0f, 0.0f, 0.0f}} + ) + .with_rotation_axis_angles({rerun::RotationAxisAngle( + {0.0f, 0.0f, 1.0f}, + rerun::Angle::degrees(static_cast(i) * 2.0f) + )}) + ); + } +} diff --git a/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.py b/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.py new file mode 100644 index 000000000000..4e17ad107a6a --- /dev/null +++ b/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.py @@ -0,0 +1,30 @@ +"""Log a simple 3D mesh with several leaf-transforms which instantiate the mesh several times and will not affect its children.""" + +import rerun as rr + +rr.init("rerun_example_mesh3d_leaf_transforms3d", spawn=True) +rr.set_time_sequence("frame", 0) + +rr.log( + "shape", + rr.Mesh3D( + vertex_positions=[[1, 1, 1], [-1, -1, 1], [-1, 1, -1], [1, -1, -1]], + triangle_indices=[[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]], + vertex_colors=[[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0]], + ), +) +# This box will not be affected by its parent's leaf transforms! +rr.log( + "shape/box", + rr.Boxes3D(half_sizes=[[5.0, 5.0, 5.0]]), +) + +for i in range(0, 100): + rr.set_time_sequence("frame", i) + rr.log( + "shape", + rr.LeafTransforms3D( + translations=[[2, 0, 0], [0, 2, 0], [0, -2, 0], [-2, 0, 0]], + rotation_axis_angles=rr.RotationAxisAngle([0, 0, 1], rr.Angle(deg=i * 2)), + ), + ) diff --git a/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.rs b/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.rs new file mode 100644 index 000000000000..90fc38e66db8 --- /dev/null +++ b/docs/snippets/all/archetypes/mesh3d_leaf_transforms3d.rs @@ -0,0 +1,44 @@ +// Log a simple 3D mesh with several leaf-transforms which instantiate the mesh several times and will not affect its children. + +fn main() -> Result<(), Box> { + let rec = + rerun::RecordingStreamBuilder::new("rerun_example_mesh3d_leaf_transforms3d").spawn()?; + + rec.set_time_sequence("frame", 0); + rec.log( + "shape", + &rerun::Mesh3D::new([ + [1.0, 1.0, 1.0], + [-1.0, -1.0, 1.0], + [-1.0, 1.0, -1.0], + [1.0, -1.0, -1.0], + ]) + .with_triangle_indices([[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]) + .with_vertex_colors([0xFF0000FF, 0x00FF00FF, 0x00000FFFF, 0xFFFF00FF]), + )?; + // This box will not be affected by its parent's leaf transforms! + rec.log( + "shape/box", + &rerun::Boxes3D::from_half_sizes([[5.0, 5.0, 5.0]]), + )?; + + for i in 0..100 { + rec.set_time_sequence("frame", i); + rec.log( + "shape", + &rerun::LeafTransforms3D::default() + .with_translations([ + [2.0, 0.0, 0.0], + [0.0, 2.0, 0.0], + [0.0, -2.0, 0.0], + [-2.0, 0.0, 0.0], + ]) + .with_rotation_axis_angles([rerun::RotationAxisAngle::new( + [0.0, 0.0, 1.0], + rerun::Angle::from_degrees(i as f32 * 2.0), + )]), + )?; + } + + Ok(()) +} diff --git a/docs/snippets/snippets.toml b/docs/snippets/snippets.toml index c77ef5e06b0f..ea368b3854a4 100644 --- a/docs/snippets/snippets.toml +++ b/docs/snippets/snippets.toml @@ -163,6 +163,17 @@ quick_start = [ # These examples don't have exactly the same implementation. "archetypes/transform3d_hierarchy" = [ # Uses a lot of trigonometry which is surprisingly easy to get the same on Rust & C++, but not on Python/Numpy "py", ] +"archetypes/leaf_transforms3d_combined" = [ # TODO(#3235): Slight floating point differences in point grid. + "cpp", + "py", + "rust", +] +"archetypes/mesh3d_leaf_transforms3d" = [ # TODO(#3235): Slight floating point differences in deg to rad conversion. + "cpp", + "py", + "rust", +] + # `$config_dir` will be replaced with the absolute path of `docs/snippets`. [extra_args] diff --git a/examples/python/signed_distance_fields/README.md b/examples/python/signed_distance_fields/README.md index 8589a6b210ad..df9a256b2135 100644 --- a/examples/python/signed_distance_fields/README.md +++ b/examples/python/signed_distance_fields/README.md @@ -40,10 +40,9 @@ center = bs2.center - bs1.center * scale ``` ```python -# Logging the 3D asset with the unit sphere -mesh3d = rr.Asset3D(path=path) -mesh3d.transform = rr.OutOfTreeTransform3DBatch(rr.TranslationRotationScale3D(translation=center, scale=scale)) -rr.log("world/mesh", mesh3d) +# Logging the 3D asset within the unit sphere +rr.log("world/mesh", rr.Asset3D(path=path)) +rr.log("world/mesh", rr.Transform3D(translation=center, scale=scale)) ``` ### Sample SDF diff --git a/examples/python/signed_distance_fields/signed_distance_fields/__main__.py b/examples/python/signed_distance_fields/signed_distance_fields/__main__.py index 108ab74e3b75..17adac1a5357 100755 --- a/examples/python/signed_distance_fields/signed_distance_fields/__main__.py +++ b/examples/python/signed_distance_fields/signed_distance_fields/__main__.py @@ -89,11 +89,8 @@ def log_mesh(path: Path, mesh: Trimesh) -> None: scale = bs2.scale / bs1.scale center = bs2.center - bs1.center * scale - mesh3d = rr.Asset3D(path=path) - mesh3d.transform = rr.OutOfTreeTransform3DBatch( - rr.datatypes.TranslationRotationScale3D(translation=center, scale=scale) - ) - rr.log("world/mesh", mesh3d) + rr.log("world/mesh", rr.Asset3D(path=path)) + rr.log("world/mesh", rr.Transform3D(translation=center, scale=scale)) def log_sampled_sdf(points: npt.NDArray[np.float32], sdf: npt.NDArray[np.float32]) -> None: diff --git a/rerun_cpp/src/rerun.hpp b/rerun_cpp/src/rerun.hpp index 261d1c7ef43e..5a82929634e9 100644 --- a/rerun_cpp/src/rerun.hpp +++ b/rerun_cpp/src/rerun.hpp @@ -38,7 +38,6 @@ namespace rerun { using components::LineStrip2D; using components::LineStrip3D; using components::MediaType; - using components::OutOfTreeTransform3D; using components::Position2D; using components::Position3D; using components::Radius; diff --git a/rerun_cpp/src/rerun/archetypes.hpp b/rerun_cpp/src/rerun/archetypes.hpp index d568c39f5943..01e040445bad 100644 --- a/rerun_cpp/src/rerun/archetypes.hpp +++ b/rerun_cpp/src/rerun/archetypes.hpp @@ -15,6 +15,7 @@ #include "archetypes/ellipsoids.hpp" #include "archetypes/image.hpp" #include "archetypes/image_encoded.hpp" +#include "archetypes/leaf_transforms3d.hpp" #include "archetypes/line_strips2d.hpp" #include "archetypes/line_strips3d.hpp" #include "archetypes/mesh3d.hpp" diff --git a/rerun_cpp/src/rerun/archetypes/.gitattributes b/rerun_cpp/src/rerun/archetypes/.gitattributes index b3776f43140a..98d5359e2cee 100644 --- a/rerun_cpp/src/rerun/archetypes/.gitattributes +++ b/rerun_cpp/src/rerun/archetypes/.gitattributes @@ -27,6 +27,8 @@ image.cpp linguist-generated=true image.hpp linguist-generated=true image_encoded.cpp linguist-generated=true image_encoded.hpp linguist-generated=true +leaf_transforms3d.cpp linguist-generated=true +leaf_transforms3d.hpp linguist-generated=true line_strips2d.cpp linguist-generated=true line_strips2d.hpp linguist-generated=true line_strips3d.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/archetypes/asset3d.cpp b/rerun_cpp/src/rerun/archetypes/asset3d.cpp index 0a02b9aec381..55bd9196dc2a 100644 --- a/rerun_cpp/src/rerun/archetypes/asset3d.cpp +++ b/rerun_cpp/src/rerun/archetypes/asset3d.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(4); + cells.reserve(3); { auto result = DataCell::from_loggable(archetype.blob); @@ -26,11 +26,6 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } - if (archetype.transform.has_value()) { - auto result = DataCell::from_loggable(archetype.transform.value()); - RR_RETURN_NOT_OK(result.error); - cells.push_back(std::move(result.value)); - } { auto indicator = Asset3D::IndicatorComponent(); auto result = DataCell::from_loggable(indicator); diff --git a/rerun_cpp/src/rerun/archetypes/asset3d.hpp b/rerun_cpp/src/rerun/archetypes/asset3d.hpp index 22131f95f6e9..88141b6b0ab8 100644 --- a/rerun_cpp/src/rerun/archetypes/asset3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/asset3d.hpp @@ -7,7 +7,6 @@ #include "../compiler_utils.hpp" #include "../components/blob.hpp" #include "../components/media_type.hpp" -#include "../components/out_of_tree_transform3d.hpp" #include "../data_cell.hpp" #include "../indicator_component.hpp" #include "../result.hpp" @@ -23,6 +22,9 @@ namespace rerun::archetypes { /// /// See also `archetypes::Mesh3D`. /// + /// If there are multiple `archetypes::LeafTransforms3D` instances logged to the same entity as a mesh, + /// an instance of the mesh will be drawn for each transform. + /// /// ## Example /// /// ### Simple 3D asset @@ -66,11 +68,6 @@ namespace rerun::archetypes { /// If it cannot guess, it won't be able to render the asset. std::optional media_type; - /// An out-of-tree transform. - /// - /// Applies a transformation to the asset itself without impacting its children. - std::optional transform; - public: static constexpr const char IndicatorComponentName[] = "rerun.components.Asset3DIndicator"; @@ -127,15 +124,6 @@ namespace rerun::archetypes { // See: https://github.com/rerun-io/rerun/issues/4027 RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } - - /// An out-of-tree transform. - /// - /// Applies a transformation to the asset itself without impacting its children. - Asset3D with_transform(rerun::components::OutOfTreeTransform3D _transform) && { - transform = std::move(_transform); - // See: https://github.com/rerun-io/rerun/issues/4027 - RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) - } }; } // namespace rerun::archetypes diff --git a/rerun_cpp/src/rerun/archetypes/leaf_transforms3d.cpp b/rerun_cpp/src/rerun/archetypes/leaf_transforms3d.cpp new file mode 100644 index 000000000000..48423d9677e3 --- /dev/null +++ b/rerun_cpp/src/rerun/archetypes/leaf_transforms3d.cpp @@ -0,0 +1,53 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs". + +#include "leaf_transforms3d.hpp" + +#include "../collection_adapter_builtins.hpp" + +namespace rerun::archetypes {} + +namespace rerun { + + Result> AsComponents::serialize( + const archetypes::LeafTransforms3D& archetype + ) { + using namespace archetypes; + std::vector cells; + cells.reserve(6); + + if (archetype.translations.has_value()) { + auto result = DataCell::from_loggable(archetype.translations.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.rotation_axis_angles.has_value()) { + auto result = DataCell::from_loggable(archetype.rotation_axis_angles.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.quaternions.has_value()) { + auto result = DataCell::from_loggable(archetype.quaternions.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.scales.has_value()) { + auto result = DataCell::from_loggable(archetype.scales.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.mat3x3.has_value()) { + auto result = DataCell::from_loggable(archetype.mat3x3.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + { + auto indicator = LeafTransforms3D::IndicatorComponent(); + auto result = DataCell::from_loggable(indicator); + RR_RETURN_NOT_OK(result.error); + cells.emplace_back(std::move(result.value)); + } + + return cells; + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/archetypes/leaf_transforms3d.hpp b/rerun_cpp/src/rerun/archetypes/leaf_transforms3d.hpp new file mode 100644 index 000000000000..30bf26d0d7f2 --- /dev/null +++ b/rerun_cpp/src/rerun/archetypes/leaf_transforms3d.hpp @@ -0,0 +1,163 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs". + +#pragma once + +#include "../collection.hpp" +#include "../compiler_utils.hpp" +#include "../components/leaf_rotation_axis_angle.hpp" +#include "../components/leaf_rotation_quat.hpp" +#include "../components/leaf_scale3d.hpp" +#include "../components/leaf_transform_mat3x3.hpp" +#include "../components/leaf_translation3d.hpp" +#include "../data_cell.hpp" +#include "../indicator_component.hpp" +#include "../result.hpp" + +#include +#include +#include +#include + +namespace rerun::archetypes { + /// **Archetype**: One or more transforms between the parent and the current entity which are *not* propagated in the transform hierarchy. + /// + /// For transforms that are propagated in the transform hierarchy, see `archetypes::Transform3D`. + /// + /// If both `archetypes::LeafTransforms3D` and `archetypes::Transform3D` are present, + /// first the tree propagating `archetypes::Transform3D` is applied, then `archetypes::LeafTransforms3D`. + /// + /// Currently, most visualizers support only a single leaf transform per entity. + /// Check archetype documentations for details - if not otherwise specified, only the first leaf transform is applied. + /// + /// From the point of view of the entity's coordinate system, + /// all components are applied in the inverse order they are listed here. + /// E.g. if both a translation and a max3x3 transform are present, + /// the 3x3 matrix is applied first, followed by the translation. + /// + /// ## Example + /// + /// ### Regular & leaf transform in tandom + /// ![image](https://static.rerun.io/leaf_transform3d/41674f0082d6de489f8a1cd1583f60f6b5820ddf/full.png) + /// + /// ```cpp + /// #include + /// #include + /// + /// int main() { + /// const auto rec = rerun::RecordingStream("rerun_example_leaf_transform3d_combined"); + /// rec.set_time_sequence("frame", 0); + /// + /// // Log a box and points further down in the hierarchy. + /// rec.log("world/box", rerun::Boxes3D::from_half_sizes({{1.0, 1.0, 1.0}})); + /// rec.log( + /// "world/box/points", + /// rerun::Points3D(rerun::demo::grid3d(-10.0f, 10.0f, 10)) + /// ); + /// + /// for (int i = 0; i <180; ++i) { + /// rec.set_time_sequence("frame", i); + /// + /// // Log a regular transform which affects both the box and the points. + /// rec.log( + /// "world/box", + /// rerun::Transform3D::from_rotation(rerun::RotationAxisAngle{ + /// {0.0f, 0.0f, 1.0f}, + /// rerun::Angle::degrees(static_cast(i) * 2.0f)}) + /// ); + /// + /// // Log an leaf transform which affects only the box. + /// rec.log( + /// "world/box", + /// rerun::LeafTransforms3D().with_translations( + /// {{0.0f, 0.0f, std::abs(static_cast(i) * 0.1f - 5.0f) - 5.0f}} + /// ) + /// ); + /// } + /// } + /// ``` + struct LeafTransforms3D { + /// Translation vectors. + std::optional> translations; + + /// Rotations via axis + angle. + std::optional> rotation_axis_angles; + + /// Rotations via quaternion. + std::optional> quaternions; + + /// Scaling factors. + std::optional> scales; + + /// 3x3 transformation matrices. + std::optional> mat3x3; + + public: + static constexpr const char IndicatorComponentName[] = + "rerun.components.LeafTransforms3DIndicator"; + + /// Indicator component, used to identify the archetype when converting to a list of components. + using IndicatorComponent = rerun::components::IndicatorComponent; + + public: + LeafTransforms3D() = default; + LeafTransforms3D(LeafTransforms3D&& other) = default; + + /// Translation vectors. + LeafTransforms3D with_translations( + Collection _translations + ) && { + translations = std::move(_translations); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Rotations via axis + angle. + LeafTransforms3D with_rotation_axis_angles( + Collection _rotation_axis_angles + ) && { + rotation_axis_angles = std::move(_rotation_axis_angles); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Rotations via quaternion. + LeafTransforms3D with_quaternions( + Collection _quaternions + ) && { + quaternions = std::move(_quaternions); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Scaling factors. + LeafTransforms3D with_scales(Collection _scales) && { + scales = std::move(_scales); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// 3x3 transformation matrices. + LeafTransforms3D with_mat3x3(Collection _mat3x3 + ) && { + mat3x3 = std::move(_mat3x3); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + }; + +} // namespace rerun::archetypes + +namespace rerun { + /// \private + template + struct AsComponents; + + /// \private + template <> + struct AsComponents { + /// Serialize all set component batches. + static Result> serialize(const archetypes::LeafTransforms3D& archetype + ); + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/archetypes/mesh3d.hpp b/rerun_cpp/src/rerun/archetypes/mesh3d.hpp index cff8d26153e1..934e9f8c6948 100644 --- a/rerun_cpp/src/rerun/archetypes/mesh3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/mesh3d.hpp @@ -27,7 +27,10 @@ namespace rerun::archetypes { /// /// See also `archetypes::Asset3D`. /// - /// ## Example + /// If there are multiple `archetypes::LeafTransforms3D` instances logged to the same entity as a mesh, + /// an instance of the mesh will be drawn for each transform. + /// + /// ## Examples /// /// ### Simple indexed 3D mesh /// ![image](https://static.rerun.io/mesh3d_simple/e1e5fd97265daf0d0bc7b782d862f19086fd6975/full.png) @@ -61,6 +64,48 @@ namespace rerun::archetypes { /// ); /// } /// ``` + /// + /// ### 3D mesh with leaf transforms + /// ![image](https://static.rerun.io/mesh3d_leaf_transforms3d/c2d0ee033129da53168f5705625a9b033f3a3d61/full.png) + /// + /// ```cpp + /// #include + /// + /// int main() { + /// const auto rec = rerun::RecordingStream("rerun_example_mesh3d_leaf_transforms3d"); + /// rec.spawn().exit_on_failure(); + /// + /// rec.set_time_sequence("frame", 0); + /// rec.log( + /// "shape", + /// rerun::Mesh3D( + /// {{1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f}, {-1.0f, 1.0f, -1.0f}, {1.0f, -1.0f, -1.0f}} + /// ) + /// .with_triangle_indices({{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3}}) + /// .with_vertex_colors({0xFF0000FF, 0x00FF00FF, 0x00000FFFF, 0xFFFF00FF}) + /// ); + /// // This box will not be affected by its parent's leaf transforms! + /// rec.log("shape/box", rerun::Boxes3D::from_half_sizes({{5.0f, 5.0f, 5.0f}})); + /// + /// for (int i = 0; i <100; ++i) { + /// rec.set_time_sequence("frame", i); + /// rec.log( + /// "shape", + /// rerun::LeafTransforms3D() + /// .with_translations( + /// {{2.0f, 0.0f, 0.0f}, + /// {0.0f, 2.0f, 0.0f}, + /// {0.0f, -2.0f, 0.0f}, + /// {-2.0f, 0.0f, 0.0f}} + /// ) + /// .with_rotation_axis_angles({rerun::RotationAxisAngle( + /// {0.0f, 0.0f, 1.0f}, + /// rerun::Angle::degrees(static_cast(i) * 2.0f) + /// )}) + /// ); + /// } + /// } + /// ``` struct Mesh3D { /// The positions of each vertex. /// diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index fd92e24cdec3..a579c12e7828 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -23,6 +23,11 @@ #include "components/half_size3d.hpp" #include "components/image_plane_distance.hpp" #include "components/keypoint_id.hpp" +#include "components/leaf_rotation_axis_angle.hpp" +#include "components/leaf_rotation_quat.hpp" +#include "components/leaf_scale3d.hpp" +#include "components/leaf_transform_mat3x3.hpp" +#include "components/leaf_translation3d.hpp" #include "components/line_strip2d.hpp" #include "components/line_strip3d.hpp" #include "components/magnification_filter.hpp" @@ -31,7 +36,6 @@ #include "components/media_type.hpp" #include "components/name.hpp" #include "components/opacity.hpp" -#include "components/out_of_tree_transform3d.hpp" #include "components/pinhole_projection.hpp" #include "components/position2d.hpp" #include "components/position3d.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index 3b125ebb1e59..943e213436a7 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -28,6 +28,11 @@ half_size2d.hpp linguist-generated=true half_size3d.hpp linguist-generated=true image_plane_distance.hpp linguist-generated=true keypoint_id.hpp linguist-generated=true +leaf_rotation_axis_angle.hpp linguist-generated=true +leaf_rotation_quat.hpp linguist-generated=true +leaf_scale3d.hpp linguist-generated=true +leaf_transform_mat3x3.hpp linguist-generated=true +leaf_translation3d.hpp linguist-generated=true line_strip2d.cpp linguist-generated=true line_strip2d.hpp linguist-generated=true line_strip3d.cpp linguist-generated=true @@ -40,7 +45,6 @@ marker_size.hpp linguist-generated=true media_type.hpp linguist-generated=true name.hpp linguist-generated=true opacity.hpp linguist-generated=true -out_of_tree_transform3d.hpp linguist-generated=true pinhole_projection.hpp linguist-generated=true position2d.hpp linguist-generated=true position3d.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/leaf_rotation_axis_angle.hpp b/rerun_cpp/src/rerun/components/leaf_rotation_axis_angle.hpp new file mode 100644 index 000000000000..ed72a67e78dc --- /dev/null +++ b/rerun_cpp/src/rerun/components/leaf_rotation_axis_angle.hpp @@ -0,0 +1,60 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs". + +#pragma once + +#include "../datatypes/rotation_axis_angle.hpp" +#include "../result.hpp" + +#include +#include + +namespace rerun::components { + /// **Component**: 3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy. + struct LeafRotationAxisAngle { + rerun::datatypes::RotationAxisAngle rotation; + + public: + LeafRotationAxisAngle() = default; + + LeafRotationAxisAngle(rerun::datatypes::RotationAxisAngle rotation_) + : rotation(rotation_) {} + + LeafRotationAxisAngle& operator=(rerun::datatypes::RotationAxisAngle rotation_) { + rotation = rotation_; + return *this; + } + + /// Cast to the underlying RotationAxisAngle datatype + operator rerun::datatypes::RotationAxisAngle() const { + return rotation; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert( + sizeof(rerun::datatypes::RotationAxisAngle) == sizeof(components::LeafRotationAxisAngle) + ); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.LeafRotationAxisAngle"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::LeafRotationAxisAngle` into an arrow array. + static Result> to_arrow( + const components::LeafRotationAxisAngle* instances, size_t num_instances + ) { + return Loggable::to_arrow( + &instances->rotation, + num_instances + ); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/leaf_rotation_quat.hpp b/rerun_cpp/src/rerun/components/leaf_rotation_quat.hpp new file mode 100644 index 000000000000..f1fed9de518a --- /dev/null +++ b/rerun_cpp/src/rerun/components/leaf_rotation_quat.hpp @@ -0,0 +1,60 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/rotation_quat.fbs". + +#pragma once + +#include "../datatypes/quaternion.hpp" +#include "../result.hpp" + +#include +#include + +namespace rerun::components { + /// **Component**: A 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy. + /// + /// Note: although the x,y,z,w components of the quaternion will be passed through to the + /// datastore as provided, when used in the Viewer, quaternions will always be normalized. + struct LeafRotationQuat { + rerun::datatypes::Quaternion quaternion; + + public: + LeafRotationQuat() = default; + + LeafRotationQuat(rerun::datatypes::Quaternion quaternion_) : quaternion(quaternion_) {} + + LeafRotationQuat& operator=(rerun::datatypes::Quaternion quaternion_) { + quaternion = quaternion_; + return *this; + } + + /// Cast to the underlying Quaternion datatype + operator rerun::datatypes::Quaternion() const { + return quaternion; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Quaternion) == sizeof(components::LeafRotationQuat)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.LeafRotationQuat"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::LeafRotationQuat` into an arrow array. + static Result> to_arrow( + const components::LeafRotationQuat* instances, size_t num_instances + ) { + return Loggable::to_arrow( + &instances->quaternion, + num_instances + ); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/leaf_scale3d.hpp b/rerun_cpp/src/rerun/components/leaf_scale3d.hpp new file mode 100644 index 000000000000..805f9bba0084 --- /dev/null +++ b/rerun_cpp/src/rerun/components/leaf_scale3d.hpp @@ -0,0 +1,89 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/scale3d.fbs". + +#pragma once + +#include "../datatypes/vec3d.hpp" +#include "../result.hpp" + +#include +#include +#include + +namespace rerun::components { + /// **Component**: A 3D scale factor that doesn't propagate in the transform hierarchy. + /// + /// A scale of 1.0 means no scaling. + /// A scale of 2.0 means doubling the size. + /// Each component scales along the corresponding axis. + struct LeafScale3D { + rerun::datatypes::Vec3D scale; + + public: + // Extensions to generated type defined in 'leaf_scale3d_ext.cpp' + + /// Construct `LeafScale3D` from x/y/z values. + LeafScale3D(float x, float y, float z) : scale{x, y, z} {} + + /// Construct `LeafScale3D` from x/y/z float pointer. + explicit LeafScale3D(const float* xyz) : scale{xyz[0], xyz[1], xyz[2]} {} + + /// Construct a `LeafScale3D` from a uniform scale factor. + explicit LeafScale3D(float uniform_scale) + : LeafScale3D(datatypes::Vec3D{uniform_scale, uniform_scale, uniform_scale}) {} + + /// Explicitly construct a `LeafScale3D` from a uniform scale factor. + static LeafScale3D uniform(float uniform_scale) { + return LeafScale3D(uniform_scale); + } + + /// Explicitly construct a `LeafScale3D` from a 3D scale factor. + static LeafScale3D three_d(datatypes::Vec3D scale) { + return LeafScale3D(scale); + } + + public: + LeafScale3D() = default; + + LeafScale3D(rerun::datatypes::Vec3D scale_) : scale(scale_) {} + + LeafScale3D& operator=(rerun::datatypes::Vec3D scale_) { + scale = scale_; + return *this; + } + + LeafScale3D(std::array xyz_) : scale(xyz_) {} + + LeafScale3D& operator=(std::array xyz_) { + scale = xyz_; + return *this; + } + + /// Cast to the underlying Vec3D datatype + operator rerun::datatypes::Vec3D() const { + return scale; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Vec3D) == sizeof(components::LeafScale3D)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.LeafScale3D"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::LeafScale3D` into an arrow array. + static Result> to_arrow( + const components::LeafScale3D* instances, size_t num_instances + ) { + return Loggable::to_arrow(&instances->scale, num_instances); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/leaf_scale3d_ext.cpp b/rerun_cpp/src/rerun/components/leaf_scale3d_ext.cpp new file mode 100644 index 000000000000..67f5706b06c8 --- /dev/null +++ b/rerun_cpp/src/rerun/components/leaf_scale3d_ext.cpp @@ -0,0 +1,27 @@ +namespace rerun::components { +#if 0 + // + + /// Construct `LeafScale3D` from x/y/z values. + LeafScale3D(float x, float y, float z) : scale{x, y, z} {} + + /// Construct `LeafScale3D` from x/y/z float pointer. + explicit LeafScale3D(const float* xyz) : scale{xyz[0], xyz[1], xyz[2]} {} + + /// Construct a `LeafScale3D` from a uniform scale factor. + explicit LeafScale3D(float uniform_scale) : LeafScale3D(datatypes::Vec3D{uniform_scale, uniform_scale, uniform_scale}) {} + + /// Explicitly construct a `LeafScale3D` from a uniform scale factor. + static LeafScale3D uniform(float uniform_scale) { + return LeafScale3D(uniform_scale); + } + + /// Explicitly construct a `LeafScale3D` from a 3D scale factor. + static LeafScale3D three_d(datatypes::Vec3D scale) { + return LeafScale3D(scale); + } + + // + +#endif +} // namespace rerun::components diff --git a/rerun_cpp/src/rerun/components/leaf_transform_mat3x3.hpp b/rerun_cpp/src/rerun/components/leaf_transform_mat3x3.hpp new file mode 100644 index 000000000000..729fff7bc896 --- /dev/null +++ b/rerun_cpp/src/rerun/components/leaf_transform_mat3x3.hpp @@ -0,0 +1,74 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/transform_mat3x3.fbs". + +#pragma once + +#include "../datatypes/mat3x3.hpp" +#include "../result.hpp" + +#include +#include +#include + +namespace rerun::components { + /// **Component**: A 3x3 transformation matrix Matrix that doesn't propagate in the transform hierarchy. + /// + /// 3x3 matrixes are able to represent any affine transformation in 3D space, + /// i.e. rotation, scaling, shearing, reflection etc. + /// + /// Matrices in Rerun are stored as flat list of coefficients in column-major order: + /// ```text + /// column 0 column 1 column 2 + /// ------------------------------------------------- + /// row 0 | flat_columns[0] flat_columns[3] flat_columns[6] + /// row 1 | flat_columns[1] flat_columns[4] flat_columns[7] + /// row 2 | flat_columns[2] flat_columns[5] flat_columns[8] + /// ``` + struct LeafTransformMat3x3 { + rerun::datatypes::Mat3x3 matrix; + + public: + LeafTransformMat3x3() = default; + + LeafTransformMat3x3(rerun::datatypes::Mat3x3 matrix_) : matrix(matrix_) {} + + LeafTransformMat3x3& operator=(rerun::datatypes::Mat3x3 matrix_) { + matrix = matrix_; + return *this; + } + + LeafTransformMat3x3(std::array flat_columns_) : matrix(flat_columns_) {} + + LeafTransformMat3x3& operator=(std::array flat_columns_) { + matrix = flat_columns_; + return *this; + } + + /// Cast to the underlying Mat3x3 datatype + operator rerun::datatypes::Mat3x3() const { + return matrix; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Mat3x3) == sizeof(components::LeafTransformMat3x3)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.LeafTransformMat3x3"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::LeafTransformMat3x3` into an arrow array. + static Result> to_arrow( + const components::LeafTransformMat3x3* instances, size_t num_instances + ) { + return Loggable::to_arrow(&instances->matrix, num_instances); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/leaf_translation3d.hpp b/rerun_cpp/src/rerun/components/leaf_translation3d.hpp new file mode 100644 index 000000000000..47c39bef9d64 --- /dev/null +++ b/rerun_cpp/src/rerun/components/leaf_translation3d.hpp @@ -0,0 +1,83 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/translation3d.fbs". + +#pragma once + +#include "../datatypes/vec3d.hpp" +#include "../result.hpp" + +#include +#include +#include + +namespace rerun::components { + /// **Component**: A translation vector in 3D space that doesn't propagate in the transform hierarchy. + struct LeafTranslation3D { + rerun::datatypes::Vec3D vector; + + public: + // Extensions to generated type defined in 'leaf_translation3d_ext.cpp' + + /// Construct `LeafTranslation3D` from x/y/z values. + LeafTranslation3D(float x, float y, float z) : vector{x, y, z} {} + + /// Construct `LeafTranslation3D` from x/y/z float pointer. + explicit LeafTranslation3D(const float* xyz) : vector{xyz[0], xyz[1], xyz[2]} {} + + float x() const { + return vector.x(); + } + + float y() const { + return vector.y(); + } + + float z() const { + return vector.z(); + } + + public: + LeafTranslation3D() = default; + + LeafTranslation3D(rerun::datatypes::Vec3D vector_) : vector(vector_) {} + + LeafTranslation3D& operator=(rerun::datatypes::Vec3D vector_) { + vector = vector_; + return *this; + } + + LeafTranslation3D(std::array xyz_) : vector(xyz_) {} + + LeafTranslation3D& operator=(std::array xyz_) { + vector = xyz_; + return *this; + } + + /// Cast to the underlying Vec3D datatype + operator rerun::datatypes::Vec3D() const { + return vector; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Vec3D) == sizeof(components::LeafTranslation3D)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.LeafTranslation3D"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::LeafTranslation3D` into an arrow array. + static Result> to_arrow( + const components::LeafTranslation3D* instances, size_t num_instances + ) { + return Loggable::to_arrow(&instances->vector, num_instances); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/leaf_translation3d_ext.cpp b/rerun_cpp/src/rerun/components/leaf_translation3d_ext.cpp new file mode 100644 index 000000000000..2ec728c70ce3 --- /dev/null +++ b/rerun_cpp/src/rerun/components/leaf_translation3d_ext.cpp @@ -0,0 +1,25 @@ +namespace rerun::components { +#if 0 + // + + /// Construct `LeafTranslation3D` from x/y/z values. + LeafTranslation3D(float x, float y, float z) : vector{x, y, z} {} + + /// Construct `LeafTranslation3D` from x/y/z float pointer. + explicit LeafTranslation3D(const float* xyz) : vector{xyz[0], xyz[1], xyz[2]} {} + + float x() const { + return vector.x(); + } + + float y() const { + return vector.y(); + } + + float z() const { + return vector.z(); + } + + // +#endif +} // namespace rerun::components diff --git a/rerun_cpp/src/rerun/components/out_of_tree_transform3d.hpp b/rerun_cpp/src/rerun/components/out_of_tree_transform3d.hpp deleted file mode 100644 index 131fb9dfe56a..000000000000 --- a/rerun_cpp/src/rerun/components/out_of_tree_transform3d.hpp +++ /dev/null @@ -1,73 +0,0 @@ -// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs -// Based on "crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs". - -#pragma once - -#include "../datatypes/transform3d.hpp" -#include "../datatypes/translation_rotation_scale3d.hpp" -#include "../result.hpp" - -#include -#include - -namespace rerun::components { - /// **Component**: An out-of-tree affine transform between two 3D spaces, represented in a given direction. - /// - /// "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it. - struct OutOfTreeTransform3D { - /// Representation of the transform. - rerun::datatypes::Transform3D repr; - - public: - OutOfTreeTransform3D() = default; - - OutOfTreeTransform3D(rerun::datatypes::Transform3D repr_) : repr(repr_) {} - - OutOfTreeTransform3D& operator=(rerun::datatypes::Transform3D repr_) { - repr = repr_; - return *this; - } - - OutOfTreeTransform3D(rerun::datatypes::TranslationRotationScale3D TranslationRotationScale_) - : repr(TranslationRotationScale_) {} - - OutOfTreeTransform3D& operator=( - rerun::datatypes::TranslationRotationScale3D TranslationRotationScale_ - ) { - repr = TranslationRotationScale_; - return *this; - } - - /// Cast to the underlying Transform3D datatype - operator rerun::datatypes::Transform3D() const { - return repr; - } - }; -} // namespace rerun::components - -namespace rerun { - static_assert( - sizeof(rerun::datatypes::Transform3D) == sizeof(components::OutOfTreeTransform3D) - ); - - /// \private - template <> - struct Loggable { - static constexpr const char Name[] = "rerun.components.OutOfTreeTransform3D"; - - /// Returns the arrow data type this type corresponds to. - static const std::shared_ptr& arrow_datatype() { - return Loggable::arrow_datatype(); - } - - /// Serializes an array of `rerun::components::OutOfTreeTransform3D` into an arrow array. - static Result> to_arrow( - const components::OutOfTreeTransform3D* instances, size_t num_instances - ) { - return Loggable::to_arrow( - &instances->repr, - num_instances - ); - } - }; -} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/rotation_quat.hpp b/rerun_cpp/src/rerun/components/rotation_quat.hpp index a34d7dc62429..4bdfc6d06ac6 100644 --- a/rerun_cpp/src/rerun/components/rotation_quat.hpp +++ b/rerun_cpp/src/rerun/components/rotation_quat.hpp @@ -13,7 +13,7 @@ namespace rerun::components { /// **Component**: A 3D rotation expressed as a quaternion. /// /// Note: although the x,y,z,w components of the quaternion will be passed through to the - /// datastore as provided, when used in the Viewer Quaternions will always be normalized. + /// datastore as provided, when used in the Viewer, quaternions will always be normalized. struct RotationQuat { rerun::datatypes::Quaternion quaternion; diff --git a/rerun_cpp/src/rerun/components/transform_relation.hpp b/rerun_cpp/src/rerun/components/transform_relation.hpp index 1a76de63a617..87ceb3e9b731 100644 --- a/rerun_cpp/src/rerun/components/transform_relation.hpp +++ b/rerun_cpp/src/rerun/components/transform_relation.hpp @@ -20,14 +20,14 @@ namespace rerun::components { /// The transform describes how to transform into the parent entity's space. /// - /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + /// E.g. a translation of (0, 1, 0) with this `components::TransformRelation` logged at `parent/child` means /// that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis. /// From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis. ParentFromChild = 1, /// The transform describes how to transform into the child entity's space. /// - /// E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + /// E.g. a translation of (0, 1, 0) with this `components::TransformRelation` logged at `parent/child` means /// that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis. /// From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis. ChildFromParent = 2, diff --git a/rerun_py/docs/gen_common_index.py b/rerun_py/docs/gen_common_index.py index 6ba6564a7e76..73edd33519ca 100755 --- a/rerun_py/docs/gen_common_index.py +++ b/rerun_py/docs/gen_common_index.py @@ -209,9 +209,8 @@ class Section: "archetypes.DisconnectedSpace", "archetypes.Pinhole", "archetypes.Transform3D", + "archetypes.LeafTransforms3D", "archetypes.ViewCoordinates", - "components.TransformMat3x3", - "components.Translation3D", "datatypes.Quaternion", "datatypes.RotationAxisAngle", "datatypes.Scale3D", diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/rerun/__init__.py index 906fc4db983c..b93b056dc3f2 100644 --- a/rerun_py/rerun_sdk/rerun/__init__.py +++ b/rerun_py/rerun_sdk/rerun/__init__.py @@ -51,6 +51,7 @@ Ellipsoids as Ellipsoids, Image as Image, ImageEncoded as ImageEncoded, + LeafTransforms3D as LeafTransforms3D, LineStrips2D as LineStrips2D, LineStrips3D as LineStrips3D, Mesh3D as Mesh3D, @@ -76,8 +77,6 @@ from .components import ( AlbedoFactor as AlbedoFactor, MediaType as MediaType, - OutOfTreeTransform3D as OutOfTreeTransform3D, - OutOfTreeTransform3DBatch as OutOfTreeTransform3DBatch, Radius as Radius, Scale3D as Scale3D, TensorDimensionIndexSelection as TensorDimensionIndexSelection, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes b/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes index 9485c225adf1..5ed3b7834283 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes @@ -15,6 +15,7 @@ disconnected_space.py linguist-generated=true ellipsoids.py linguist-generated=true image.py linguist-generated=true image_encoded.py linguist-generated=true +leaf_transforms3d.py linguist-generated=true line_strips2d.py linguist-generated=true line_strips3d.py linguist-generated=true mesh3d.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/archetypes/__init__.py b/rerun_py/rerun_sdk/rerun/archetypes/__init__.py index f5d60e03a309..70acdc671da6 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/__init__.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/__init__.py @@ -15,6 +15,7 @@ from .ellipsoids import Ellipsoids from .image import Image from .image_encoded import ImageEncoded +from .leaf_transforms3d import LeafTransforms3D from .line_strips2d import LineStrips2D from .line_strips3d import LineStrips3D from .mesh3d import Mesh3D @@ -45,6 +46,7 @@ "Ellipsoids", "Image", "ImageEncoded", + "LeafTransforms3D", "LineStrips2D", "LineStrips3D", "Mesh3D", diff --git a/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py b/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py index abe22e53df63..f442198762b6 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/asset3d.py @@ -23,6 +23,9 @@ class Asset3D(Asset3DExt, Archetype): See also [`archetypes.Mesh3D`][rerun.archetypes.Mesh3D]. + If there are multiple [`archetypes.LeafTransforms3D`][rerun.archetypes.LeafTransforms3D] instances logged to the same entity as a mesh, + an instance of the mesh will be drawn for each transform. + Example ------- ### Simple 3D asset: @@ -59,7 +62,6 @@ def __attrs_clear__(self) -> None: self.__attrs_init__( blob=None, # type: ignore[arg-type] media_type=None, # type: ignore[arg-type] - transform=None, # type: ignore[arg-type] ) @classmethod @@ -95,16 +97,5 @@ def _clear(cls) -> Asset3D: # # (Docstring intentionally commented out to hide this field from the docs) - transform: components.OutOfTreeTransform3DBatch | None = field( - metadata={"component": "optional"}, - default=None, - converter=components.OutOfTreeTransform3DBatch._optional, # type: ignore[misc] - ) - # An out-of-tree transform. - # - # Applies a transformation to the asset itself without impacting its children. - # - # (Docstring intentionally commented out to hide this field from the docs) - __str__ = Archetype.__str__ __repr__ = Archetype.__repr__ # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py index 92e7882d510d..d47ba735620e 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/asset3d_ext.py @@ -35,7 +35,6 @@ def __init__( path: str | pathlib.Path | None = None, contents: datatypes.BlobLike | None = None, media_type: datatypes.Utf8Like | None = None, - transform: datatypes.Transform3DLike | None = None, ): """ Create a new instance of the Asset3D archetype. @@ -63,11 +62,6 @@ def __init__( or the viewer will try to guess from the contents (magic header). If the media type cannot be guessed, the viewer won't be able to render the asset. - transform: - An out-of-tree transform. - - Applies a transformation to the asset itself without impacting its children. - """ with catch_and_log_exceptions(context=self.__class__.__name__): @@ -81,7 +75,7 @@ def __init__( if media_type is None: media_type = guess_media_type(str(path)) - self.__attrs_init__(blob=blob, media_type=media_type, transform=transform) + self.__attrs_init__(blob=blob, media_type=media_type) return self.__attrs_clear__() diff --git a/rerun_py/rerun_sdk/rerun/archetypes/leaf_transforms3d.py b/rerun_py/rerun_sdk/rerun/archetypes/leaf_transforms3d.py new file mode 100644 index 000000000000..17c1a84b3780 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/archetypes/leaf_transforms3d.py @@ -0,0 +1,177 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/archetypes/leaf_transforms3d.fbs". + +# You can extend this class by creating a "LeafTransforms3DExt" class in "leaf_transforms3d_ext.py". + +from __future__ import annotations + +from typing import Any + +from attrs import define, field + +from .. import components, datatypes +from .._baseclasses import ( + Archetype, +) +from ..error_utils import catch_and_log_exceptions + +__all__ = ["LeafTransforms3D"] + + +@define(str=False, repr=False, init=False) +class LeafTransforms3D(Archetype): + """ + **Archetype**: One or more transforms between the parent and the current entity which are *not* propagated in the transform hierarchy. + + For transforms that are propagated in the transform hierarchy, see [`archetypes.Transform3D`][rerun.archetypes.Transform3D]. + + If both [`archetypes.LeafTransforms3D`][rerun.archetypes.LeafTransforms3D] and [`archetypes.Transform3D`][rerun.archetypes.Transform3D] are present, + first the tree propagating [`archetypes.Transform3D`][rerun.archetypes.Transform3D] is applied, then [`archetypes.LeafTransforms3D`][rerun.archetypes.LeafTransforms3D]. + + Currently, most visualizers support only a single leaf transform per entity. + Check archetype documentations for details - if not otherwise specified, only the first leaf transform is applied. + + From the point of view of the entity's coordinate system, + all components are applied in the inverse order they are listed here. + E.g. if both a translation and a max3x3 transform are present, + the 3x3 matrix is applied first, followed by the translation. + + Example + ------- + ### Regular & leaf transform in tandom: + ```python + import numpy as np + import rerun as rr + + rr.init("rerun_example_leaf_transform3d_combined", spawn=True) + + rr.set_time_sequence("frame", 0) + + # Log a box and points further down in the hierarchy. + rr.log("world/box", rr.Boxes3D(half_sizes=[[1.0, 1.0, 1.0]])) + rr.log("world/box/points", rr.Points3D(np.vstack([xyz.ravel() for xyz in np.mgrid[3 * [slice(-10, 10, 10j)]]]).T)) + + for i in range(0, 180): + rr.set_time_sequence("frame", i) + + # Log a regular transform which affects both the box and the points. + rr.log("world/box", rr.Transform3D(rotation_axis_angle=rr.RotationAxisAngle([0, 0, 1], angle=rr.Angle(deg=i * 2)))) + + # Log an leaf transform which affects only the box. + rr.log("world/box", rr.LeafTransforms3D(translations=[0, 0, abs(i * 0.1 - 5.0) - 5.0])) + ``` +
+ + + + + + + +
+ + """ + + def __init__( + self: Any, + *, + translations: datatypes.Vec3DArrayLike | None = None, + rotation_axis_angles: datatypes.RotationAxisAngleArrayLike | None = None, + quaternions: datatypes.QuaternionArrayLike | None = None, + scales: datatypes.Vec3DArrayLike | None = None, + mat3x3: datatypes.Mat3x3ArrayLike | None = None, + ): + """ + Create a new instance of the LeafTransforms3D archetype. + + Parameters + ---------- + translations: + Translation vectors. + rotation_axis_angles: + Rotations via axis + angle. + quaternions: + Rotations via quaternion. + scales: + Scaling factors. + mat3x3: + 3x3 transformation matrices. + + """ + + # You can define your own __init__ function as a member of LeafTransforms3DExt in leaf_transforms3d_ext.py + with catch_and_log_exceptions(context=self.__class__.__name__): + self.__attrs_init__( + translations=translations, + rotation_axis_angles=rotation_axis_angles, + quaternions=quaternions, + scales=scales, + mat3x3=mat3x3, + ) + return + self.__attrs_clear__() + + def __attrs_clear__(self) -> None: + """Convenience method for calling `__attrs_init__` with all `None`s.""" + self.__attrs_init__( + translations=None, # type: ignore[arg-type] + rotation_axis_angles=None, # type: ignore[arg-type] + quaternions=None, # type: ignore[arg-type] + scales=None, # type: ignore[arg-type] + mat3x3=None, # type: ignore[arg-type] + ) + + @classmethod + def _clear(cls) -> LeafTransforms3D: + """Produce an empty LeafTransforms3D, bypassing `__init__`.""" + inst = cls.__new__(cls) + inst.__attrs_clear__() + return inst + + translations: components.LeafTranslation3DBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.LeafTranslation3DBatch._optional, # type: ignore[misc] + ) + # Translation vectors. + # + # (Docstring intentionally commented out to hide this field from the docs) + + rotation_axis_angles: components.LeafRotationAxisAngleBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.LeafRotationAxisAngleBatch._optional, # type: ignore[misc] + ) + # Rotations via axis + angle. + # + # (Docstring intentionally commented out to hide this field from the docs) + + quaternions: components.LeafRotationQuatBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.LeafRotationQuatBatch._optional, # type: ignore[misc] + ) + # Rotations via quaternion. + # + # (Docstring intentionally commented out to hide this field from the docs) + + scales: components.LeafScale3DBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.LeafScale3DBatch._optional, # type: ignore[misc] + ) + # Scaling factors. + # + # (Docstring intentionally commented out to hide this field from the docs) + + mat3x3: components.LeafTransformMat3x3Batch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.LeafTransformMat3x3Batch._optional, # type: ignore[misc] + ) + # 3x3 transformation matrices. + # + # (Docstring intentionally commented out to hide this field from the docs) + + __str__ = Archetype.__str__ + __repr__ = Archetype.__repr__ # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py index 1b7e7ab53308..3aa2a3d5c043 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/mesh3d.py @@ -23,8 +23,11 @@ class Mesh3D(Mesh3DExt, Archetype): See also [`archetypes.Asset3D`][rerun.archetypes.Asset3D]. - Example - ------- + If there are multiple [`archetypes.LeafTransforms3D`][rerun.archetypes.LeafTransforms3D] instances logged to the same entity as a mesh, + an instance of the mesh will be drawn for each transform. + + Examples + -------- ### Simple indexed 3D mesh: ```python import rerun as rr @@ -51,6 +54,47 @@ class Mesh3D(Mesh3DExt, Archetype): + ### 3D mesh with leaf transforms: + ```python + import rerun as rr + + rr.init("rerun_example_mesh3d_leaf_transforms3d", spawn=True) + rr.set_time_sequence("frame", 0) + + rr.log( + "shape", + rr.Mesh3D( + vertex_positions=[[1, 1, 1], [-1, -1, 1], [-1, 1, -1], [1, -1, -1]], + triangle_indices=[[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]], + vertex_colors=[[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0]], + ), + ) + # This box will not be affected by its parent's leaf transforms! + rr.log( + "shape/box", + rr.Boxes3D(half_sizes=[[5.0, 5.0, 5.0]]), + ) + + for i in range(0, 100): + rr.set_time_sequence("frame", i) + rr.log( + "shape", + rr.LeafTransforms3D( + translations=[[2, 0, 0], [0, 2, 0], [0, -2, 0], [-2, 0, 0]], + rotation_axis_angles=rr.RotationAxisAngle([0, 0, 1], rr.Angle(deg=i * 2)), + ), + ) + ``` +
+ + + + + + + +
+ """ # __init__ can be found in mesh3d_ext.py diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index 97a701b83707..5240df29aa9e 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -23,6 +23,11 @@ half_size2d.py linguist-generated=true half_size3d.py linguist-generated=true image_plane_distance.py linguist-generated=true keypoint_id.py linguist-generated=true +leaf_rotation_axis_angle.py linguist-generated=true +leaf_rotation_quat.py linguist-generated=true +leaf_scale3d.py linguist-generated=true +leaf_transform_mat3x3.py linguist-generated=true +leaf_translation3d.py linguist-generated=true line_strip2d.py linguist-generated=true line_strip3d.py linguist-generated=true magnification_filter.py linguist-generated=true @@ -31,7 +36,6 @@ marker_size.py linguist-generated=true media_type.py linguist-generated=true name.py linguist-generated=true opacity.py linguist-generated=true -out_of_tree_transform3d.py linguist-generated=true pinhole_projection.py linguist-generated=true position2d.py linguist-generated=true position3d.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 85d82926cc28..1f1a7da35b77 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -41,6 +41,11 @@ from .half_size3d import HalfSize3D, HalfSize3DBatch, HalfSize3DType from .image_plane_distance import ImagePlaneDistance, ImagePlaneDistanceBatch, ImagePlaneDistanceType from .keypoint_id import KeypointId, KeypointIdBatch, KeypointIdType +from .leaf_rotation_axis_angle import LeafRotationAxisAngle, LeafRotationAxisAngleBatch, LeafRotationAxisAngleType +from .leaf_rotation_quat import LeafRotationQuat, LeafRotationQuatBatch, LeafRotationQuatType +from .leaf_scale3d import LeafScale3D, LeafScale3DBatch, LeafScale3DType +from .leaf_transform_mat3x3 import LeafTransformMat3x3, LeafTransformMat3x3Batch, LeafTransformMat3x3Type +from .leaf_translation3d import LeafTranslation3D, LeafTranslation3DBatch, LeafTranslation3DType from .line_strip2d import LineStrip2D, LineStrip2DArrayLike, LineStrip2DBatch, LineStrip2DLike, LineStrip2DType from .line_strip3d import LineStrip3D, LineStrip3DArrayLike, LineStrip3DBatch, LineStrip3DLike, LineStrip3DType from .magnification_filter import ( @@ -55,7 +60,6 @@ from .media_type import MediaType, MediaTypeBatch, MediaTypeType from .name import Name, NameBatch, NameType from .opacity import Opacity, OpacityBatch, OpacityType -from .out_of_tree_transform3d import OutOfTreeTransform3D, OutOfTreeTransform3DBatch, OutOfTreeTransform3DType from .pinhole_projection import PinholeProjection, PinholeProjectionBatch, PinholeProjectionType from .position2d import Position2D, Position2DBatch, Position2DType from .position3d import Position3D, Position3DBatch, Position3DType @@ -171,6 +175,21 @@ "KeypointId", "KeypointIdBatch", "KeypointIdType", + "LeafRotationAxisAngle", + "LeafRotationAxisAngleBatch", + "LeafRotationAxisAngleType", + "LeafRotationQuat", + "LeafRotationQuatBatch", + "LeafRotationQuatType", + "LeafScale3D", + "LeafScale3DBatch", + "LeafScale3DType", + "LeafTransformMat3x3", + "LeafTransformMat3x3Batch", + "LeafTransformMat3x3Type", + "LeafTranslation3D", + "LeafTranslation3DBatch", + "LeafTranslation3DType", "LineStrip2D", "LineStrip2DArrayLike", "LineStrip2DBatch", @@ -203,9 +222,6 @@ "Opacity", "OpacityBatch", "OpacityType", - "OutOfTreeTransform3D", - "OutOfTreeTransform3DBatch", - "OutOfTreeTransform3DType", "PinholeProjection", "PinholeProjectionBatch", "PinholeProjectionType", diff --git a/rerun_py/rerun_sdk/rerun/components/leaf_rotation_axis_angle.py b/rerun_py/rerun_sdk/rerun/components/leaf_rotation_axis_angle.py new file mode 100644 index 000000000000..04464c38fcf1 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/leaf_rotation_axis_angle.py @@ -0,0 +1,36 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs". + +# You can extend this class by creating a "LeafRotationAxisAngleExt" class in "leaf_rotation_axis_angle_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["LeafRotationAxisAngle", "LeafRotationAxisAngleBatch", "LeafRotationAxisAngleType"] + + +class LeafRotationAxisAngle(datatypes.RotationAxisAngle, ComponentMixin): + """**Component**: 3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy.""" + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of LeafRotationAxisAngleExt in leaf_rotation_axis_angle_ext.py + + # Note: there are no fields here because LeafRotationAxisAngle delegates to datatypes.RotationAxisAngle + pass + + +class LeafRotationAxisAngleType(datatypes.RotationAxisAngleType): + _TYPE_NAME: str = "rerun.components.LeafRotationAxisAngle" + + +class LeafRotationAxisAngleBatch(datatypes.RotationAxisAngleBatch, ComponentBatchMixin): + _ARROW_TYPE = LeafRotationAxisAngleType() + + +# This is patched in late to avoid circular dependencies. +LeafRotationAxisAngle._BATCH_TYPE = LeafRotationAxisAngleBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/components/leaf_rotation_quat.py b/rerun_py/rerun_sdk/rerun/components/leaf_rotation_quat.py new file mode 100644 index 000000000000..860693c48637 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/leaf_rotation_quat.py @@ -0,0 +1,41 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/rotation_quat.fbs". + +# You can extend this class by creating a "LeafRotationQuatExt" class in "leaf_rotation_quat_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["LeafRotationQuat", "LeafRotationQuatBatch", "LeafRotationQuatType"] + + +class LeafRotationQuat(datatypes.Quaternion, ComponentMixin): + """ + **Component**: A 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy. + + Note: although the x,y,z,w components of the quaternion will be passed through to the + datastore as provided, when used in the Viewer, quaternions will always be normalized. + """ + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of LeafRotationQuatExt in leaf_rotation_quat_ext.py + + # Note: there are no fields here because LeafRotationQuat delegates to datatypes.Quaternion + pass + + +class LeafRotationQuatType(datatypes.QuaternionType): + _TYPE_NAME: str = "rerun.components.LeafRotationQuat" + + +class LeafRotationQuatBatch(datatypes.QuaternionBatch, ComponentBatchMixin): + _ARROW_TYPE = LeafRotationQuatType() + + +# This is patched in late to avoid circular dependencies. +LeafRotationQuat._BATCH_TYPE = LeafRotationQuatBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/components/leaf_scale3d.py b/rerun_py/rerun_sdk/rerun/components/leaf_scale3d.py new file mode 100644 index 000000000000..99b9aae4502a --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/leaf_scale3d.py @@ -0,0 +1,43 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/scale3d.fbs". + +# You can extend this class by creating a "LeafScale3DExt" class in "leaf_scale3d_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) +from .leaf_scale3d_ext import LeafScale3DExt + +__all__ = ["LeafScale3D", "LeafScale3DBatch", "LeafScale3DType"] + + +class LeafScale3D(LeafScale3DExt, datatypes.Vec3D, ComponentMixin): + """ + **Component**: A 3D scale factor that doesn't propagate in the transform hierarchy. + + A scale of 1.0 means no scaling. + A scale of 2.0 means doubling the size. + Each component scales along the corresponding axis. + """ + + _BATCH_TYPE = None + # __init__ can be found in leaf_scale3d_ext.py + + # Note: there are no fields here because LeafScale3D delegates to datatypes.Vec3D + pass + + +class LeafScale3DType(datatypes.Vec3DType): + _TYPE_NAME: str = "rerun.components.LeafScale3D" + + +class LeafScale3DBatch(datatypes.Vec3DBatch, ComponentBatchMixin): + _ARROW_TYPE = LeafScale3DType() + + +# This is patched in late to avoid circular dependencies. +LeafScale3D._BATCH_TYPE = LeafScale3DBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/components/leaf_scale3d_ext.py b/rerun_py/rerun_sdk/rerun/components/leaf_scale3d_ext.py new file mode 100644 index 000000000000..cd8fe78ca338 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/leaf_scale3d_ext.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union + +if TYPE_CHECKING: + from rerun.datatypes import Float32Like, Vec3DLike + + +class LeafScale3DExt: + """Extension for [LeafScale3D][rerun.components.LeafScale3D].""" + + def __init__( + self: Any, + uniform_or_per_axis: Union[Vec3DLike, Float32Like] = True, + ): + """ + 3D scaling factor. + + A scale of 1.0 means no scaling. + A scale of 2.0 means doubling the size. + Each component scales along the corresponding axis. + + Parameters + ---------- + uniform_or_per_axis: + If a single value is given, it is applied the same to all three axis (uniform scaling). + + """ + if not hasattr(uniform_or_per_axis, "__len__") or len(uniform_or_per_axis) == 1: # type: ignore[arg-type] + self.__attrs_init__([uniform_or_per_axis, uniform_or_per_axis, uniform_or_per_axis]) + else: + self.__attrs_init__(uniform_or_per_axis) diff --git a/rerun_py/rerun_sdk/rerun/components/leaf_transform_mat3x3.py b/rerun_py/rerun_sdk/rerun/components/leaf_transform_mat3x3.py new file mode 100644 index 000000000000..3dfd11033cee --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/leaf_transform_mat3x3.py @@ -0,0 +1,72 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/transform_mat3x3.fbs". + +# You can extend this class by creating a "LeafTransformMat3x3Ext" class in "leaf_transform_mat3x3_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["LeafTransformMat3x3", "LeafTransformMat3x3Batch", "LeafTransformMat3x3Type"] + + +class LeafTransformMat3x3(datatypes.Mat3x3, ComponentMixin): + """ + **Component**: A 3x3 transformation matrix Matrix that doesn't propagate in the transform hierarchy. + + 3x3 matrixes are able to represent any affine transformation in 3D space, + i.e. rotation, scaling, shearing, reflection etc. + + Matrices in Rerun are stored as flat list of coefficients in column-major order: + ```text + column 0 column 1 column 2 + ------------------------------------------------- + row 0 | flat_columns[0] flat_columns[3] flat_columns[6] + row 1 | flat_columns[1] flat_columns[4] flat_columns[7] + row 2 | flat_columns[2] flat_columns[5] flat_columns[8] + ``` + + However, construction is done from a list of rows, which follows NumPy's convention: + ```python + np.testing.assert_array_equal( + rr.components.LeafTransformMat3x3([1, 2, 3, 4, 5, 6, 7, 8, 9]).flat_columns, np.array([1, 4, 7, 2, 5, 8, 3, 6, 9], dtype=np.float32) + ) + np.testing.assert_array_equal( + rr.components.LeafTransformMat3x3([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).flat_columns, + np.array([1, 4, 7, 2, 5, 8, 3, 6, 9], dtype=np.float32), + ) + ``` + If you want to construct a matrix from a list of columns instead, use the named `columns` parameter: + ```python + np.testing.assert_array_equal( + rr.components.LeafTransformMat3x3(columns=[1, 2, 3, 4, 5, 6, 7, 8, 9]).flat_columns, + np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float32), + ) + np.testing.assert_array_equal( + rr.components.LeafTransformMat3x3(columns=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]).flat_columns, + np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float32), + ) + ``` + """ + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of LeafTransformMat3x3Ext in leaf_transform_mat3x3_ext.py + + # Note: there are no fields here because LeafTransformMat3x3 delegates to datatypes.Mat3x3 + pass + + +class LeafTransformMat3x3Type(datatypes.Mat3x3Type): + _TYPE_NAME: str = "rerun.components.LeafTransformMat3x3" + + +class LeafTransformMat3x3Batch(datatypes.Mat3x3Batch, ComponentBatchMixin): + _ARROW_TYPE = LeafTransformMat3x3Type() + + +# This is patched in late to avoid circular dependencies. +LeafTransformMat3x3._BATCH_TYPE = LeafTransformMat3x3Batch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/components/leaf_translation3d.py b/rerun_py/rerun_sdk/rerun/components/leaf_translation3d.py new file mode 100644 index 000000000000..cac6ea63d266 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/leaf_translation3d.py @@ -0,0 +1,36 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/translation3d.fbs". + +# You can extend this class by creating a "LeafTranslation3DExt" class in "leaf_translation3d_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["LeafTranslation3D", "LeafTranslation3DBatch", "LeafTranslation3DType"] + + +class LeafTranslation3D(datatypes.Vec3D, ComponentMixin): + """**Component**: A translation vector in 3D space that doesn't propagate in the transform hierarchy.""" + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of LeafTranslation3DExt in leaf_translation3d_ext.py + + # Note: there are no fields here because LeafTranslation3D delegates to datatypes.Vec3D + pass + + +class LeafTranslation3DType(datatypes.Vec3DType): + _TYPE_NAME: str = "rerun.components.LeafTranslation3D" + + +class LeafTranslation3DBatch(datatypes.Vec3DBatch, ComponentBatchMixin): + _ARROW_TYPE = LeafTranslation3DType() + + +# This is patched in late to avoid circular dependencies. +LeafTranslation3D._BATCH_TYPE = LeafTranslation3DBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform3d.py b/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform3d.py deleted file mode 100644 index 26de7e765666..000000000000 --- a/rerun_py/rerun_sdk/rerun/components/out_of_tree_transform3d.py +++ /dev/null @@ -1,40 +0,0 @@ -# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs -# Based on "crates/store/re_types/definitions/rerun/components/out_of_tree_transform3d.fbs". - -# You can extend this class by creating a "OutOfTreeTransform3DExt" class in "out_of_tree_transform3d_ext.py". - -from __future__ import annotations - -from .. import datatypes -from .._baseclasses import ( - ComponentBatchMixin, - ComponentMixin, -) - -__all__ = ["OutOfTreeTransform3D", "OutOfTreeTransform3DBatch", "OutOfTreeTransform3DType"] - - -class OutOfTreeTransform3D(datatypes.Transform3D, ComponentMixin): - """ - **Component**: An out-of-tree affine transform between two 3D spaces, represented in a given direction. - - "Out-of-tree" means that the transform only affects its own entity: children don't inherit from it. - """ - - _BATCH_TYPE = None - # You can define your own __init__ function as a member of OutOfTreeTransform3DExt in out_of_tree_transform3d_ext.py - - # Note: there are no fields here because OutOfTreeTransform3D delegates to datatypes.Transform3D - pass - - -class OutOfTreeTransform3DType(datatypes.Transform3DType): - _TYPE_NAME: str = "rerun.components.OutOfTreeTransform3D" - - -class OutOfTreeTransform3DBatch(datatypes.Transform3DBatch, ComponentBatchMixin): - _ARROW_TYPE = OutOfTreeTransform3DType() - - -# This is patched in late to avoid circular dependencies. -OutOfTreeTransform3D._BATCH_TYPE = OutOfTreeTransform3DBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/components/rotation_quat.py b/rerun_py/rerun_sdk/rerun/components/rotation_quat.py index b042f6000e77..52588d8443a1 100644 --- a/rerun_py/rerun_sdk/rerun/components/rotation_quat.py +++ b/rerun_py/rerun_sdk/rerun/components/rotation_quat.py @@ -19,7 +19,7 @@ class RotationQuat(datatypes.Quaternion, ComponentMixin): **Component**: A 3D rotation expressed as a quaternion. Note: although the x,y,z,w components of the quaternion will be passed through to the - datastore as provided, when used in the Viewer Quaternions will always be normalized. + datastore as provided, when used in the Viewer, quaternions will always be normalized. """ _BATCH_TYPE = None diff --git a/rerun_py/rerun_sdk/rerun/components/transform_relation.py b/rerun_py/rerun_sdk/rerun/components/transform_relation.py index 71ec11127c6c..e9bead75f441 100644 --- a/rerun_py/rerun_sdk/rerun/components/transform_relation.py +++ b/rerun_py/rerun_sdk/rerun/components/transform_relation.py @@ -34,7 +34,7 @@ class TransformRelation(Enum): """ The transform describes how to transform into the parent entity's space. - E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + E.g. a translation of (0, 1, 0) with this [`components.TransformRelation`][rerun.components.TransformRelation] logged at `parent/child` means that from the point of view of `parent`, `parent/child` is translated 1 unit along `parent`'s Y axis. From perspective of `parent/child`, the `parent` entity is translated -1 unit along `parent/child`'s Y axis. """ @@ -43,7 +43,7 @@ class TransformRelation(Enum): """ The transform describes how to transform into the child entity's space. - E.g. a translation of (0, 1, 0) with this `TransformRelation` logged at `parent/child` means + E.g. a translation of (0, 1, 0) with this [`components.TransformRelation`][rerun.components.TransformRelation] logged at `parent/child` means that from the point of view of `parent`, `parent/child` is translated -1 unit along `parent`'s Y axis. From perspective of `parent/child`, the `parent` entity is translated 1 unit along `parent/child`'s Y axis. """ diff --git a/rerun_py/tests/unit/test_asset3d.py b/rerun_py/tests/unit/test_asset3d.py index 24ed86b448a2..e50beae90f03 100644 --- a/rerun_py/tests/unit/test_asset3d.py +++ b/rerun_py/tests/unit/test_asset3d.py @@ -26,16 +26,3 @@ def test_asset3d() -> None: for asset in assets: assert asset.blob.as_arrow_array() == rr.components.BlobBatch(blob_comp).as_arrow_array() assert asset.media_type == rr.components.MediaTypeBatch(rr.components.MediaType.GLB) - assert asset.transform is None - - -def test_asset3d_transform() -> None: - asset = rr.Asset3D(path=CUBE_FILEPATH, transform=rr.datatypes.TranslationRotationScale3D(translation=[1, 2, 3])) - - assert asset.transform is not None - assert ( - asset.transform.as_arrow_array() - == rr.components.OutOfTreeTransform3DBatch( - rr.datatypes.TranslationRotationScale3D(translation=[1, 2, 3]) - ).as_arrow_array() - ) diff --git a/rerun_py/tests/unit/test_leaf_transforms3d.py b/rerun_py/tests/unit/test_leaf_transforms3d.py new file mode 100644 index 000000000000..f0df1d4d57a7 --- /dev/null +++ b/rerun_py/tests/unit/test_leaf_transforms3d.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import itertools +from fractions import Fraction +from typing import Optional, cast + +import rerun as rr +from rerun.datatypes import ( + Quaternion, + RotationAxisAngle, +) + +from .test_matnxn import MAT_3X3_INPUT +from .test_vecnd import VEC_3D_INPUT + +SCALE_3D_INPUT = [ + # Uniform + 4, + 4.0, + Fraction(8, 2), + # ThreeD + *VEC_3D_INPUT, +] + + +def test_leaf_transform3d() -> None: + rotation_axis_angle_arrays = [ + None, + RotationAxisAngle([1, 2, 3], rr.Angle(deg=10)), + [RotationAxisAngle([1, 2, 3], rr.Angle(deg=10)), RotationAxisAngle([3, 2, 1], rr.Angle(rad=1))], + ] + quaternion_arrays = [ + None, + Quaternion(xyzw=[1, 2, 3, 4]), + [Quaternion(xyzw=[4, 3, 2, 1]), Quaternion(xyzw=[1, 2, 3, 4])], + ] + + # TODO(andreas): It would be nice to support scalar values here + scale_arrays = [None, [1.0, 2.0, 3.0], [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]] + + # TODO(#6831): repopulate this list with all transform variants + all_arrays = itertools.zip_longest( + VEC_3D_INPUT + [None], + rotation_axis_angle_arrays, + quaternion_arrays, + scale_arrays, + MAT_3X3_INPUT + [None], + ) + + for ( + translation, + rotation_axis_angle, + quaternion, + scale, + mat3x3, + ) in all_arrays: + translations = cast(Optional[rr.datatypes.Vec3DArrayLike], translation) + rotation_axis_angles = cast(Optional[rr.datatypes.RotationAxisAngleArrayLike], rotation_axis_angle) + quaternions = cast(Optional[rr.datatypes.QuaternionArrayLike], quaternion) + scales = cast(Optional[rr.datatypes.Vec3DArrayLike | rr.datatypes.Float32Like], scale) + mat3x3 = cast(Optional[rr.datatypes.Mat3x3ArrayLike], mat3x3) + + print( + f"rr.LeafTransform3D(\n" + f" translations={translations!r}\n" # + f" rotation_axis_angles={rotation_axis_angles!r}\n" # + f" quaternions={quaternions!r}\n" # + f" scales={scales!r}\n" # + f" mat3x3={mat3x3!r}\n" # + f")" + ) + arch = rr.LeafTransforms3D( + translations=translations, + rotation_axis_angles=rotation_axis_angles, + quaternions=quaternions, + scales=scales, + mat3x3=mat3x3, + ) + print(f"{arch}\n") + + assert arch.translations == rr.components.LeafTranslation3DBatch._optional(translations) + assert arch.rotation_axis_angles == rr.components.LeafRotationAxisAngleBatch._optional(rotation_axis_angles) + assert arch.quaternions == rr.components.LeafRotationQuatBatch._optional(quaternions) + assert arch.scales == rr.components.LeafScale3DBatch._optional(scales) + assert arch.mat3x3 == rr.components.LeafTransformMat3x3Batch._optional(mat3x3) diff --git a/tests/python/release_checklist/check_all_components_ui.py b/tests/python/release_checklist/check_all_components_ui.py index 3bf6b106d497..d51ba681ae9a 100644 --- a/tests/python/release_checklist/check_all_components_ui.py +++ b/tests/python/release_checklist/check_all_components_ui.py @@ -122,13 +122,6 @@ def alternatives(self) -> list[Any] | None: "MarkerSizeBatch": TestCase(batch=[5.0, 1.0, 2.0]), "MediaTypeBatch": TestCase("application/jpg"), "NameBatch": TestCase(batch=["Hello World", "Foo Bar", "Baz Qux"]), - "OutOfTreeTransform3DBatch": TestCase( - alternatives=[ - rr.datatypes.TranslationRotationScale3D( - translation=(1, 2, 3), rotation=rr.datatypes.Quaternion(xyzw=[0, 0, 0, 1]), scale=(1, 1, 1) - ), - ] - ), "OpacityBatch": TestCase(0.5), "PinholeProjectionBatch": TestCase([(0, 1, 2), (3, 4, 5), (6, 7, 8)]), "Position2DBatch": TestCase(batch=[(0, 1), (2, 3), (4, 5)]),