From 7e68a467c7aacaad51b687f8b9c1734a3fc989e1 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Mon, 19 Jan 2026 20:44:54 -0300 Subject: [PATCH 01/35] Overlay to debug information of Tween objects --- crates/tween/Cargo.toml | 3 + crates/tween/src/lib.rs | 7 +- crates/tween/src/tween_debug.rs | 263 ++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 crates/tween/src/tween_debug.rs diff --git a/crates/tween/Cargo.toml b/crates/tween/Cargo.toml index b65393d0b..d357e3a76 100644 --- a/crates/tween/Cargo.toml +++ b/crates/tween/Cargo.toml @@ -3,6 +3,9 @@ name = "tween" version = "0.1.0" edition = "2021" +[features] +tween_debug = [] + [lib] [dependencies] diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index bc493e916..0f8062983 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "tween_debug")] +mod tween_debug; + use bevy::prelude::*; use common::sets::SceneSets; use dcl::interface::{ComponentPosition, CrdtType}; @@ -9,7 +12,6 @@ use dcl_component::{ transform_and_parent::DclTransformAndParent, SceneComponentId, }; - use scene_material::SceneMaterial; use scene_runner::{ renderer_context::RendererSceneContext, update_world::AddCrdtInterfaceExt, ContainerEntity, @@ -149,6 +151,9 @@ impl Plugin for TweenPlugin { ); app.add_systems(Update, update_tween.in_set(SceneSets::PostLoop)); app.add_systems(PostUpdate, update_system_tween); + + #[cfg(feature = "tween_debug")] + app.add_plugins(tween_debug::TweenDebugPlugin); } } diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs new file mode 100644 index 000000000..38e0e31ff --- /dev/null +++ b/crates/tween/src/tween_debug.rs @@ -0,0 +1,263 @@ +use std::fmt::Debug; + +use bevy::{ + prelude::*, + text::{FontSmoothing, LineHeight}, +}; +use common::structs::PrimaryCamera; +use dcl_component::proto_components::sdk::components::pb_tween::Mode; + +use crate::Tween; + +const DEFAULT_FONT: TextFont = TextFont { + font: Handle::Weak(AssetId::Uuid { + uuid: AssetId::::DEFAULT_UUID, + }), + font_size: 8., + line_height: LineHeight::RelativeToFont(1.2), + font_smoothing: FontSmoothing::AntiAliased, +}; + +pub struct TweenDebugPlugin; + +impl Plugin for TweenDebugPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Startup, setup); + + app.add_observer(new_tween); + app.add_observer(tween_dropped); + + app.add_systems( + PostUpdate, + update_plate_position.after(TransformSystem::TransformPropagate), + ); + } +} + +#[derive(Component)] +struct TweenDebugPlane; + +#[derive(Component)] +#[relationship(relationship_target = TweenedEntity)] +struct TweenDebugPlate(Entity); + +#[derive(Component)] +#[relationship_target(relationship = TweenDebugPlate )] +struct TweenedEntity(Entity); + +fn setup(mut commands: Commands) { + commands.spawn(( + TweenDebugPlane, + Node { + width: Val::Percent(100.), + height: Val::Percent(100.), + ..Default::default() + }, + GlobalZIndex(1_000_000), + )); +} + +fn new_tween( + trigger: Trigger, + mut commands: Commands, + tweens: Query<&Tween>, + tween_debug_plane: Single>, +) { + let entity = trigger.target(); + let Ok(tween) = tweens.get(entity) else { + unreachable!("Tween must be available."); + }; + + let root = commands + .spawn(( + Node { + display: Display::Grid, + position_type: PositionType::Absolute, + column_gap: Val::Px(4.), + ..Default::default() + }, + ChildOf(*tween_debug_plane), + TweenDebugPlate(entity), + BackgroundColor(Color::BLACK.with_alpha(0.5)), + )) + .id(); + match &tween.0.mode { + Some(Mode::Move(data)) => { + plate_head(&mut commands, 1, root, "Move"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row( + &mut commands, + 6, + root, + "Face direction", + &data.face_direction, + ); + plate_display_row(&mut commands, 7, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 8, + root, + "Current time", + &tween.0.current_time, + ); + } + Some(Mode::Rotate(data)) => { + plate_head(&mut commands, 1, root, "Rotate"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row(&mut commands, 6, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 7, + root, + "Current time", + &tween.0.current_time, + ); + } + Some(Mode::Scale(data)) => { + plate_head(&mut commands, 1, root, "Scale"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row(&mut commands, 6, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 7, + root, + "Current time", + &tween.0.current_time, + ); + } + Some(Mode::TextureMove(data)) => { + plate_head(&mut commands, 1, root, "TextureMove"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row( + &mut commands, + 6, + root, + "MovementType", + &data.movement_type(), + ); + plate_display_row(&mut commands, 7, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 8, + root, + "Current time", + &tween.0.current_time, + ); + } + _ => {} + } +} + +fn tween_dropped( + trigger: Trigger, + mut commands: Commands, + tweens: Query<&TweenedEntity>, +) { + let entity = trigger.target(); + let Ok(tweened_entity) = tweens.get(entity) else { + unreachable!("Tween entity without a plate."); + }; + + commands.entity(*tweened_entity.collection()).despawn(); +} + +fn update_plate_position( + primary_camera: Single<(&Camera, &GlobalTransform), With>, + tweens: Query<(&GlobalTransform, &TweenedEntity), With>, + mut tween_plates: Query<(&mut Node, &ComputedNode), With>, +) { + let (camera, camera_global_transform) = primary_camera.into_inner(); + for (global_transform, tweened_entity) in tweens { + let Ok((mut node, computed_node)) = tween_plates.get_mut(*tweened_entity.collection()) + else { + unreachable!("TweenDebugPlate without Node."); + }; + if let Ok(viewport_position) = + camera.world_to_viewport(camera_global_transform, global_transform.translation()) + { + node.display = Display::Grid; + node.left = Val::Px(viewport_position.x - computed_node.content_size.x / 2.); + node.top = Val::Px(viewport_position.y - computed_node.content_size.y / 2.); + } else { + node.display = Display::None; + } + } +} + +fn plate_head(commands: &mut Commands, row: i16, parent: Entity, text: &str) -> impl Bundle { + commands.spawn(( + Node { + grid_row: GridPlacement::start(row), + grid_column: GridPlacement::start(1), + ..Default::default() + }, + Text::new(text), + DEFAULT_FONT, + ChildOf(parent), + )); +} + +fn plate_display_row( + commands: &mut Commands, + row: i16, + parent: Entity, + text: &str, + data: &T, +) -> impl Bundle { + commands.spawn(( + Node { + grid_row: GridPlacement::start(row), + grid_column: GridPlacement::start(1), + ..Default::default() + }, + Text::new(text), + DEFAULT_FONT, + ChildOf(parent), + )); + commands.spawn(( + Node { + grid_row: GridPlacement::start(row), + grid_column: GridPlacement::start(2), + ..Default::default() + }, + Text::new(format!("{:?}", data)), + DEFAULT_FONT, + ChildOf(parent), + )); +} From 82f22eacb8713863c87d378d03305074ec652f0f Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 08:49:05 -0300 Subject: [PATCH 02/35] Increase background alpha and update z-index --- crates/tween/src/tween_debug.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index 38e0e31ff..f342dbb36 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -78,7 +78,7 @@ fn new_tween( }, ChildOf(*tween_debug_plane), TweenDebugPlate(entity), - BackgroundColor(Color::BLACK.with_alpha(0.5)), + BackgroundColor(Color::BLACK.with_alpha(0.75)), )) .id(); match &tween.0.mode { @@ -198,22 +198,26 @@ fn tween_dropped( } fn update_plate_position( + mut commands: Commands, primary_camera: Single<(&Camera, &GlobalTransform), With>, tweens: Query<(&GlobalTransform, &TweenedEntity), With>, mut tween_plates: Query<(&mut Node, &ComputedNode), With>, ) { let (camera, camera_global_transform) = primary_camera.into_inner(); for (global_transform, tweened_entity) in tweens { - let Ok((mut node, computed_node)) = tween_plates.get_mut(*tweened_entity.collection()) - else { + let tween_debug_plate_entity = *tweened_entity.collection(); + let Ok((mut node, computed_node)) = tween_plates.get_mut(tween_debug_plate_entity) else { unreachable!("TweenDebugPlate without Node."); }; - if let Ok(viewport_position) = - camera.world_to_viewport(camera_global_transform, global_transform.translation()) + if let Ok(viewport_position) = camera + .world_to_viewport_with_depth(camera_global_transform, global_transform.translation()) { node.display = Display::Grid; node.left = Val::Px(viewport_position.x - computed_node.content_size.x / 2.); node.top = Val::Px(viewport_position.y - computed_node.content_size.y / 2.); + commands + .entity(tween_debug_plate_entity) + .insert(GlobalZIndex(-viewport_position.z as i32)); } else { node.display = Display::None; } From da52e526a15a2f3fbd716a074bcf62800ae94215 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 09:43:22 -0300 Subject: [PATCH 03/35] Make `TweenDebugPlate` only show on picking events --- Cargo.lock | 25 +++ Cargo.toml | 2 + crates/tween/src/tween_debug.rs | 320 +++++++++++++++++--------------- src/main.rs | 1 + 4 files changed, 202 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2688ad903..e3fc34736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1435,6 +1435,7 @@ dependencies = [ "bevy_log", "bevy_math", "bevy_pbr", + "bevy_picking", "bevy_platform", "bevy_ptr", "bevy_reflect", @@ -1594,6 +1595,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "bevy_picking" +version = "0.16.1" +source = "git+https://github.com/robtfm/bevy?branch=release-0.16-dcl#11ad9a79a1395e73f809390d019f76cc0d0e7a30" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bevy_window", + "crossbeam-channel", + "tracing", + "uuid 1.17.0", +] + [[package]] name = "bevy_platform" version = "0.16.1" diff --git a/Cargo.toml b/Cargo.toml index e2a4b6d8f..a177c74ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,9 @@ bevy = { version = "0.16.1", default-features = false, features=[ "bevy_scene", "bevy_winit", "bevy_core_pipeline", + "bevy_mesh_picking_backend", "bevy_pbr", + "bevy_picking", "bevy_gltf", "bevy_render", "bevy_sprite", diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index f342dbb36..e5a98f4c0 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -24,13 +24,17 @@ impl Plugin for TweenDebugPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup); - app.add_observer(new_tween); - app.add_observer(tween_dropped); + if app.is_plugin_added::() { + app.add_systems( + PostUpdate, + update_plate_position.after(TransformSystem::TransformPropagate), + ); - app.add_systems( - PostUpdate, - update_plate_position.after(TransformSystem::TransformPropagate), - ); + app.add_observer(tween_picking); + app.add_observer(tween_out); + } else { + error!("MeshPickingPlugin not added to the app. Tween picking systems are disabled.") + } } } @@ -57,146 +61,6 @@ fn setup(mut commands: Commands) { )); } -fn new_tween( - trigger: Trigger, - mut commands: Commands, - tweens: Query<&Tween>, - tween_debug_plane: Single>, -) { - let entity = trigger.target(); - let Ok(tween) = tweens.get(entity) else { - unreachable!("Tween must be available."); - }; - - let root = commands - .spawn(( - Node { - display: Display::Grid, - position_type: PositionType::Absolute, - column_gap: Val::Px(4.), - ..Default::default() - }, - ChildOf(*tween_debug_plane), - TweenDebugPlate(entity), - BackgroundColor(Color::BLACK.with_alpha(0.75)), - )) - .id(); - match &tween.0.mode { - Some(Mode::Move(data)) => { - plate_head(&mut commands, 1, root, "Move"); - plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); - plate_display_row( - &mut commands, - 3, - root, - "Easing function", - &tween.0.easing_function(), - ); - plate_display_row(&mut commands, 4, root, "Start", &data.start); - plate_display_row(&mut commands, 5, root, "End", &data.end); - plate_display_row( - &mut commands, - 6, - root, - "Face direction", - &data.face_direction, - ); - plate_display_row(&mut commands, 7, root, "Playing", &tween.0.playing); - plate_display_row( - &mut commands, - 8, - root, - "Current time", - &tween.0.current_time, - ); - } - Some(Mode::Rotate(data)) => { - plate_head(&mut commands, 1, root, "Rotate"); - plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); - plate_display_row( - &mut commands, - 3, - root, - "Easing function", - &tween.0.easing_function(), - ); - plate_display_row(&mut commands, 4, root, "Start", &data.start); - plate_display_row(&mut commands, 5, root, "End", &data.end); - plate_display_row(&mut commands, 6, root, "Playing", &tween.0.playing); - plate_display_row( - &mut commands, - 7, - root, - "Current time", - &tween.0.current_time, - ); - } - Some(Mode::Scale(data)) => { - plate_head(&mut commands, 1, root, "Scale"); - plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); - plate_display_row( - &mut commands, - 3, - root, - "Easing function", - &tween.0.easing_function(), - ); - plate_display_row(&mut commands, 4, root, "Start", &data.start); - plate_display_row(&mut commands, 5, root, "End", &data.end); - plate_display_row(&mut commands, 6, root, "Playing", &tween.0.playing); - plate_display_row( - &mut commands, - 7, - root, - "Current time", - &tween.0.current_time, - ); - } - Some(Mode::TextureMove(data)) => { - plate_head(&mut commands, 1, root, "TextureMove"); - plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); - plate_display_row( - &mut commands, - 3, - root, - "Easing function", - &tween.0.easing_function(), - ); - plate_display_row(&mut commands, 4, root, "Start", &data.start); - plate_display_row(&mut commands, 5, root, "End", &data.end); - plate_display_row( - &mut commands, - 6, - root, - "MovementType", - &data.movement_type(), - ); - plate_display_row(&mut commands, 7, root, "Playing", &tween.0.playing); - plate_display_row( - &mut commands, - 8, - root, - "Current time", - &tween.0.current_time, - ); - } - _ => {} - } -} - -fn tween_dropped( - trigger: Trigger, - mut commands: Commands, - tweens: Query<&TweenedEntity>, -) { - let entity = trigger.target(); - let Ok(tweened_entity) = tweens.get(entity) else { - unreachable!("Tween entity without a plate."); - }; - - commands.entity(*tweened_entity.collection()).despawn(); -} - fn update_plate_position( mut commands: Commands, primary_camera: Single<(&Camera, &GlobalTransform), With>, @@ -223,6 +87,170 @@ fn update_plate_position( } } } +fn tween_picking( + mut trigger: Trigger>, + mut commands: Commands, + tweens: Query<(&Tween, Has)>, + tween_debug_plane: Single>, +) { + let entity = trigger.target(); + if let Ok((tween, has_tween_debug_plate)) = tweens.get(entity) { + trigger.propagate(false); + + if has_tween_debug_plate { + return; + } + + let root = commands + .spawn(( + Node { + display: Display::Grid, + position_type: PositionType::Absolute, + column_gap: Val::Px(4.), + ..Default::default() + }, + ChildOf(*tween_debug_plane), + TweenDebugPlate(entity), + BackgroundColor(Color::BLACK.with_alpha(0.75)), + )) + .id(); + match &tween.0.mode { + Some(Mode::Move(data)) => { + plate_head(&mut commands, 1, root, "Move"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row( + &mut commands, + 6, + root, + "Face direction", + &data.face_direction, + ); + plate_display_row(&mut commands, 7, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 8, + root, + "Current time", + &tween.0.current_time, + ); + } + Some(Mode::Rotate(data)) => { + plate_head(&mut commands, 1, root, "Rotate"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row(&mut commands, 6, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 7, + root, + "Current time", + &tween.0.current_time, + ); + } + Some(Mode::Scale(data)) => { + plate_head(&mut commands, 1, root, "Scale"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row(&mut commands, 6, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 7, + root, + "Current time", + &tween.0.current_time, + ); + } + Some(Mode::TextureMove(data)) => { + plate_head(&mut commands, 1, root, "TextureMove"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Start", &data.start); + plate_display_row(&mut commands, 5, root, "End", &data.end); + plate_display_row( + &mut commands, + 6, + root, + "MovementType", + &data.movement_type(), + ); + plate_display_row(&mut commands, 7, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 8, + root, + "Current time", + &tween.0.current_time, + ); + } + Some(Mode::RotateContinuous(data)) => { + plate_head(&mut commands, 1, root, "Continuous rotation"); + plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); + plate_display_row( + &mut commands, + 3, + root, + "Easing function", + &tween.0.easing_function(), + ); + plate_display_row(&mut commands, 4, root, "Direction", &data.direction); + plate_display_row(&mut commands, 5, root, "Speed", &data.speed); + plate_display_row(&mut commands, 6, root, "Playing", &tween.0.playing); + plate_display_row( + &mut commands, + 7, + root, + "Current time", + &tween.0.current_time, + ); + } + _ => {} + } + } +} + +fn tween_out( + mut trigger: Trigger>, + mut commands: Commands, + tweens: Query<&TweenedEntity>, +) { + let entity = trigger.target(); + if let Ok(tweened_entity) = tweens.get(entity) { + trigger.propagate(false); + + commands.entity(*tweened_entity.collection()).despawn(); + }; +} fn plate_head(commands: &mut Commands, row: i16, parent: Entity, text: &str) -> impl Bundle { commands.spawn(( diff --git a/src/main.rs b/src/main.rs index 8b9658d5c..1d049b881 100644 --- a/src/main.rs +++ b/src/main.rs @@ -368,6 +368,7 @@ fn main() { }) .add_before::(NftReaderPlugin), ); + app.add_plugins(MeshPickingPlugin); app.add_plugins(EmbedAssetsPlugin); From ea888b28e7ca389c4fd9a8be0edf6a3637829530 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 09:45:26 -0300 Subject: [PATCH 04/35] Add `RotateContinuous` proto message --- .../src/proto/decentraland/sdk/components/tween.proto | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto b/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto index 2ed6d7840..371b8a268 100644 --- a/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto +++ b/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto @@ -16,6 +16,7 @@ message PBTween { Rotate rotate = 4; Scale scale = 5; TextureMove texture_move = 8; + RotateContinuous rotate_continuous = 9; } optional bool playing = 6; // default true (pause or running) @@ -46,6 +47,11 @@ message TextureMove { optional TextureMovementType movement_type = 3; // default = TextureMovementType.TMT_OFFSET } +message RotateContinuous { + decentraland.common.Quaternion direction = 1; + float speed = 2; +} + enum TextureMovementType { TMT_OFFSET = 0; // default = TextureMovementType.TMT_OFFSET TMT_TILING = 1; From 80dec2adb2c9e492abc6666f16619a0272ba81a8 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 11:26:46 -0300 Subject: [PATCH 05/35] Implement `RotateContinuous` tween --- crates/tween/Cargo.toml | 1 + crates/tween/src/lib.rs | 250 +++++++++++++++++++++++++------- crates/tween/src/tween_debug.rs | 42 ++++++ 3 files changed, 242 insertions(+), 51 deletions(-) diff --git a/crates/tween/Cargo.toml b/crates/tween/Cargo.toml index d357e3a76..b9b924cc3 100644 --- a/crates/tween/Cargo.toml +++ b/crates/tween/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [features] tween_debug = [] +alt_rotate_continuous = [] [lib] diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index 0f8062983..18b220806 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -28,6 +28,13 @@ impl From for Tween { } impl Tween { + fn is_continuous(&self) -> bool { + match self.0.mode { + Some(Mode::RotateContinuous(_)) => true, + _ => false, + } + } + fn apply( &self, time: f32, @@ -133,6 +140,43 @@ impl Tween { } } } + Some(Mode::RotateContinuous(data)) => { + #[cfg(not(feature = "alt_rotate_continuous"))] + { + // The rotation is desired. + // The speed and time is provided. + // The rotation is then calculated by integrating the + // speed function. + // The integral of a constant speed is `speed * time`. + let startup_factor = if self.0.duration > 0. { todo!() } else { 0. }; + let post_startup = if time > self.0.duration { + ((time - self.0.duration) / 1000.) * data.speed.to_radians() + } else { + 0. + }; + let dcl_quat = data.direction.unwrap().to_bevy_normalized(); + let axis = dcl_quat * Vec3::NEG_Y; + transform.rotation = Quat::from_axis_angle(axis, startup_factor + post_startup); + } + #[cfg(feature = "alt_rotate_continuous")] + { + // The rotation is desired. + // The speed and time is provided. + // The rotation is then calculated by integrating the + // speed function. + // The integral of a constant speed is `speed * time`. + let startup_factor = if self.0.duration > 0. { todo!() } else { 0. }; + let post_startup = if time > self.0.duration { + ((time - self.0.duration) / 1000.) * data.speed.to_radians() + } else { + 0. + }; + let dcl_quat = data.direction.unwrap(); + let (axis, angle) = dcl_quat.to_bevy_normalized().to_axis_angle(); + transform.rotation = + Quat::from_axis_angle(axis, angle + startup_factor + post_startup); + } + } _ => {} } } @@ -175,69 +219,173 @@ pub fn update_tween( materials: ResMut>, ) { let materials = materials.into_inner(); - for (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat) in tweens.iter_mut() { - let playing = tween.0.playing.unwrap_or(true); - let delta = if playing { - time.delta_secs() * 1000.0 / tween.0.duration - } else { - 0.0 + for (ent, scene_ent, parent, tween, transform, state, maybe_h_mat) in tweens.iter_mut() { + let Ok(scene) = scenes.get_mut(scene_ent.root) else { + continue; }; - let updated_time = if tween.is_changed() { - tween.0.current_time.unwrap_or(0.0) + if tween.is_continuous() { + continuous_tween_update( + &mut commands, + (ent, scene_ent, parent, tween, transform, state, maybe_h_mat), + scene, + parents, + materials, + &time, + ); } else { - state - .as_ref() - .map(|state| state.0.current_time + delta) - .unwrap_or(0.0) - .min(1.0) - }; + discrete_tween_update( + &mut commands, + (ent, scene_ent, parent, tween, transform, state, maybe_h_mat), + scene, + parents, + materials, + &time, + ); + } + } +} + +type DiscreteTweenComponents<'a> = ( + Entity, + &'a ContainerEntity, + &'a ChildOf, + Ref<'a, Tween>, + Mut<'a, Transform>, + Option>, + Option<&'a MeshMaterial3d>, +); + +fn discrete_tween_update( + commands: &mut Commands, + (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat): DiscreteTweenComponents, + mut scene: Mut, + parents: Query<&SceneEntity>, + materials: &mut Assets, + time: &Time, +) { + let playing = tween.0.playing.unwrap_or(true); + let delta = if playing { + time.delta_secs() * 1000.0 / tween.0.duration + } else { + 0.0 + }; + + let updated_time = if tween.is_changed() { + tween.0.current_time.unwrap_or(0.0) + } else { + state + .as_ref() + .map(|state| state.0.current_time + delta) + .unwrap_or(0.0) + .min(1.0) + }; + + let updated_status = if playing && updated_time == 1.0 { + TweenStateStatus::TsCompleted + } else if playing { + TweenStateStatus::TsActive + } else { + TweenStateStatus::TsPaused + }; + + let updated_state = TweenState(PbTweenState { + state: updated_status as i32, + current_time: updated_time, + }); - let updated_status = if playing && updated_time == 1.0 { - TweenStateStatus::TsCompleted - } else if playing { - TweenStateStatus::TsActive + if state.as_deref() != Some(&updated_state) { + scene.update_crdt( + SceneComponentId::TWEEN_STATE, + CrdtType::LWW_ENT, + scene_ent.container_id, + &updated_state.0, + ); + + if let Some(mut state) = state { + state.0 = updated_state.0; } else { - TweenStateStatus::TsPaused + commands.entity(ent).try_insert(updated_state); + } + + tween.apply(updated_time, &mut transform, maybe_h_mat, materials); + + let Ok(parent) = parents.get(parent.parent()) else { + warn!("no parent for tweened ent"); + return; }; - let updated_state = TweenState(PbTweenState { - state: updated_status as i32, - current_time: updated_time, - }); - - if state.as_deref() != Some(&updated_state) { - let Ok(mut scene) = scenes.get_mut(scene_ent.root) else { - continue; - }; - - scene.update_crdt( - SceneComponentId::TWEEN_STATE, - CrdtType::LWW_ENT, - scene_ent.container_id, - &updated_state.0, - ); + scene.update_crdt( + SceneComponentId::TRANSFORM, + CrdtType::LWW_ENT, + scene_ent.container_id, + &DclTransformAndParent::from_bevy_transform_and_parent(&transform, parent.id), + ); + } +} - if let Some(mut state) = state { - state.0 = updated_state.0; - } else { - commands.entity(ent).try_insert(updated_state); - } +fn continuous_tween_update( + commands: &mut Commands, + (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat): DiscreteTweenComponents, + mut scene: Mut, + parents: Query<&SceneEntity>, + materials: &mut Assets, + time: &Time, +) { + let playing = tween.0.playing.unwrap_or(true); + let delta = if playing { + time.delta_secs() * 1000. + } else { + 0.0 + }; - tween.apply(updated_time, &mut transform, maybe_h_mat, materials); + let updated_time = if tween.is_changed() { + tween.0.current_time.unwrap_or(0.0) + } else { + state + .as_ref() + .map(|state| state.0.current_time + delta) + .unwrap_or(0.0) + }; - let Ok(parent) = parents.get(parent.parent()) else { - warn!("no parent for tweened ent"); - continue; - }; + let updated_status = if playing { + TweenStateStatus::TsActive + } else { + TweenStateStatus::TsPaused + }; - scene.update_crdt( - SceneComponentId::TRANSFORM, - CrdtType::LWW_ENT, - scene_ent.container_id, - &DclTransformAndParent::from_bevy_transform_and_parent(&transform, parent.id), - ); + let updated_state = TweenState(PbTweenState { + state: updated_status as i32, + current_time: updated_time, + }); + + if state.as_deref() != Some(&updated_state) { + scene.update_crdt( + SceneComponentId::TWEEN_STATE, + CrdtType::LWW_ENT, + scene_ent.container_id, + &updated_state.0, + ); + + if let Some(mut state) = state { + state.0 = updated_state.0; + } else { + commands.entity(ent).try_insert(updated_state); } + + tween.apply(updated_time, &mut transform, maybe_h_mat, materials); + + let Ok(parent) = parents.get(parent.parent()) else { + warn!("no parent for tweened ent"); + return; + }; + + scene.update_crdt( + SceneComponentId::TRANSFORM, + CrdtType::LWW_ENT, + scene_ent.container_id, + &DclTransformAndParent::from_bevy_transform_and_parent(&transform, parent.id), + ); } } diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index e5a98f4c0..5cf421f28 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use bevy::{ + color::palettes, prelude::*, text::{FontSmoothing, LineHeight}, }; @@ -32,6 +33,11 @@ impl Plugin for TweenDebugPlugin { app.add_observer(tween_picking); app.add_observer(tween_out); + + app.add_systems( + PostUpdate, + axis_gizmos.after(TransformSystem::TransformPropagate), + ); } else { error!("MeshPickingPlugin not added to the app. Tween picking systems are disabled.") } @@ -293,3 +299,39 @@ fn plate_display_row( ChildOf(parent), )); } + +fn axis_gizmos(mut gizmos: Gizmos, tweens: Query<(&Tween, &GlobalTransform)>) { + for (tween, global_transform) in tweens { + match &tween.0.mode { + #[cfg(not(feature = "alt_rotate_continuous"))] + Some(Mode::RotateContinuous(data)) => { + let direction = data.direction.unwrap().to_bevy_normalized(); + gizmos.axes( + Isometry3d::from_translation(global_transform.translation()), + 2.5, + ); + gizmos.arrow( + global_transform.translation(), + global_transform.translation() + direction * Vec3::NEG_Y * 2.5, + palettes::tailwind::RED_700, + ); + } + #[cfg(feature = "alt_rotate_continuous")] + Some(Mode::RotateContinuous(data)) => { + let direction = data.direction.unwrap(); + let (axis, _) = Quat::from_xyzw(direction.x, direction.y, direction.z, direction.w) + .to_axis_angle(); + gizmos.axes( + Isometry3d::from_translation(global_transform.translation()), + 2.5, + ); + gizmos.arrow( + global_transform.translation(), + global_transform.translation() + axis * 2.5, + palettes::tailwind::RED_700, + ); + } + _ => {} + } + } +} From 6f3f355b931cead8a309718999212a5741cee9c7 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 11:58:19 -0300 Subject: [PATCH 06/35] Correct `RotateContinuous` alternative calculation to use Z-up --- crates/tween/src/lib.rs | 7 +++++-- crates/tween/src/tween_debug.rs | 14 +++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index 18b220806..b7b26b3e9 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -173,8 +173,11 @@ impl Tween { }; let dcl_quat = data.direction.unwrap(); let (axis, angle) = dcl_quat.to_bevy_normalized().to_axis_angle(); - transform.rotation = - Quat::from_axis_angle(axis, angle + startup_factor + post_startup); + let corrected_axis = Vec3::new(axis.x, -axis.z, -axis.y); + transform.rotation = Quat::from_axis_angle( + corrected_axis, + angle + startup_factor + post_startup, + ); } } _ => {} diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index 5cf421f28..94ac8c015 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -307,7 +307,10 @@ fn axis_gizmos(mut gizmos: Gizmos, tweens: Query<(&Tween, &GlobalTransform)>) { Some(Mode::RotateContinuous(data)) => { let direction = data.direction.unwrap().to_bevy_normalized(); gizmos.axes( - Isometry3d::from_translation(global_transform.translation()), + Isometry3d::new( + global_transform.translation(), + Quat::from_axis_angle(Vec3::X, -90.0f32.to_radians()), + ), 2.5, ); gizmos.arrow( @@ -319,15 +322,16 @@ fn axis_gizmos(mut gizmos: Gizmos, tweens: Query<(&Tween, &GlobalTransform)>) { #[cfg(feature = "alt_rotate_continuous")] Some(Mode::RotateContinuous(data)) => { let direction = data.direction.unwrap(); - let (axis, _) = Quat::from_xyzw(direction.x, direction.y, direction.z, direction.w) - .to_axis_angle(); + let (axis, _) = direction.to_bevy_normalized().to_axis_angle(); + let correction = Quat::from_axis_angle(Vec3::X, -90.0f32.to_radians()); + let corrected_axis = Vec3::new(axis.x, -axis.z, -axis.y); gizmos.axes( - Isometry3d::from_translation(global_transform.translation()), + Isometry3d::new(global_transform.translation(), correction), 2.5, ); gizmos.arrow( global_transform.translation(), - global_transform.translation() + axis * 2.5, + global_transform.translation() + corrected_axis * 2.5, palettes::tailwind::RED_700, ); } From 14383bba402f25b83e455bc6e12219a1486e2221 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 17:07:39 -0300 Subject: [PATCH 07/35] Create a feature for ADR-285 --- crates/tween/Cargo.toml | 4 +++- crates/tween/src/lib.rs | 23 +++++++++++++++++++++-- crates/tween/src/tween_debug.rs | 20 ++++++++++++-------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/crates/tween/Cargo.toml b/crates/tween/Cargo.toml index b9b924cc3..8cb2d75f9 100644 --- a/crates/tween/Cargo.toml +++ b/crates/tween/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2021" [features] +default = ["adr285"] tween_debug = [] -alt_rotate_continuous = [] +adr285 = [] +alt_rotate_continuous = ["adr285"] [lib] diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index b7b26b3e9..96633311f 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -1,6 +1,9 @@ #[cfg(feature = "tween_debug")] mod tween_debug; +#[cfg(all(feature = "adr285", not(feature = "alt_rotate_continuous")))] +use std::f32::consts::FRAC_2_PI; + use bevy::prelude::*; use common::sets::SceneSets; use dcl::interface::{ComponentPosition, CrdtType}; @@ -28,6 +31,7 @@ impl From for Tween { } impl Tween { + #[cfg(feature = "adr285")] fn is_continuous(&self) -> bool { match self.0.mode { Some(Mode::RotateContinuous(_)) => true, @@ -140,6 +144,7 @@ impl Tween { } } } + #[cfg(feature = "adr285")] Some(Mode::RotateContinuous(data)) => { #[cfg(not(feature = "alt_rotate_continuous"))] { @@ -154,8 +159,11 @@ impl Tween { } else { 0. }; - let dcl_quat = data.direction.unwrap().to_bevy_normalized(); - let axis = dcl_quat * Vec3::NEG_Y; + let dcl_quat = data.direction.unwrap(); + // +Z forward to Bevy's -Z forward + let quat = + dcl_quat.to_bevy_normalized() * Quat::from_axis_angle(Vec3::Y, FRAC_2_PI); + let axis = quat * Vec3::NEG_Y; transform.rotation = Quat::from_axis_angle(axis, startup_factor + post_startup); } #[cfg(feature = "alt_rotate_continuous")] @@ -227,6 +235,7 @@ pub fn update_tween( continue; }; + #[cfg(feature = "adr285")] if tween.is_continuous() { continuous_tween_update( &mut commands, @@ -246,6 +255,15 @@ pub fn update_tween( &time, ); } + #[cfg(not(feature = "adr285"))] + discrete_tween_update( + &mut commands, + (ent, scene_ent, parent, tween, transform, state, maybe_h_mat), + scene, + parents, + materials, + &time, + ); } } @@ -327,6 +345,7 @@ fn discrete_tween_update( } } +#[cfg(feature = "adr285")] fn continuous_tween_update( commands: &mut Commands, (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat): DiscreteTweenComponents, diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index 94ac8c015..ac9377a5a 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -1,3 +1,5 @@ +#[cfg(all(feature = "adr285", not(feature = "alt_rotate_continuous")))] +use std::f32::consts::FRAC_2_PI; use std::fmt::Debug; use bevy::{ @@ -219,6 +221,7 @@ fn tween_picking( &tween.0.current_time, ); } + #[cfg(feature = "adr285")] Some(Mode::RotateContinuous(data)) => { plate_head(&mut commands, 1, root, "Continuous rotation"); plate_display_row(&mut commands, 2, root, "Duration", &tween.0.duration); @@ -303,23 +306,24 @@ fn plate_display_row( fn axis_gizmos(mut gizmos: Gizmos, tweens: Query<(&Tween, &GlobalTransform)>) { for (tween, global_transform) in tweens { match &tween.0.mode { - #[cfg(not(feature = "alt_rotate_continuous"))] + #[cfg(all(feature = "adr285", not(feature = "alt_rotate_continuous")))] Some(Mode::RotateContinuous(data)) => { - let direction = data.direction.unwrap().to_bevy_normalized(); + let dcl_quat = data.direction.unwrap(); + // +Z forward to Bevy's -Z forward + let quat = + dcl_quat.to_bevy_normalized() * Quat::from_axis_angle(Vec3::Y, FRAC_2_PI); + let axis = quat * Vec3::NEG_Y; gizmos.axes( - Isometry3d::new( - global_transform.translation(), - Quat::from_axis_angle(Vec3::X, -90.0f32.to_radians()), - ), + Isometry3d::from_translation(global_transform.translation()), 2.5, ); gizmos.arrow( global_transform.translation(), - global_transform.translation() + direction * Vec3::NEG_Y * 2.5, + global_transform.translation() + axis * 2.5, palettes::tailwind::RED_700, ); } - #[cfg(feature = "alt_rotate_continuous")] + #[cfg(all(feature = "adr285", feature = "alt_rotate_continuous"))] Some(Mode::RotateContinuous(data)) => { let direction = data.direction.unwrap(); let (axis, _) = direction.to_bevy_normalized().to_axis_angle(); From 8d7d7b935b7be3e499297f93bc56730e5861bbe2 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 17:13:44 -0300 Subject: [PATCH 08/35] Rename type alias --- crates/tween/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index 96633311f..f0e22dcda 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -267,7 +267,7 @@ pub fn update_tween( } } -type DiscreteTweenComponents<'a> = ( +type TweenUpdateComponents<'a> = ( Entity, &'a ContainerEntity, &'a ChildOf, @@ -279,7 +279,7 @@ type DiscreteTweenComponents<'a> = ( fn discrete_tween_update( commands: &mut Commands, - (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat): DiscreteTweenComponents, + (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat): TweenUpdateComponents, mut scene: Mut, parents: Query<&SceneEntity>, materials: &mut Assets, @@ -348,7 +348,7 @@ fn discrete_tween_update( #[cfg(feature = "adr285")] fn continuous_tween_update( commands: &mut Commands, - (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat): DiscreteTweenComponents, + (ent, scene_ent, parent, tween, mut transform, state, maybe_h_mat): TweenUpdateComponents, mut scene: Mut, parents: Query<&SceneEntity>, materials: &mut Assets, From 000b35f7630aff686cdfee6b338519fe44825718 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 17:26:18 -0300 Subject: [PATCH 09/35] Add the other 2 continuous tween types and update `is_continuous` --- .../proto/decentraland/sdk/components/tween.proto | 13 +++++++++++++ crates/tween/src/lib.rs | 12 ++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto b/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto index 371b8a268..f95e188e3 100644 --- a/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto +++ b/crates/dcl_component/src/proto/decentraland/sdk/components/tween.proto @@ -17,6 +17,8 @@ message PBTween { Scale scale = 5; TextureMove texture_move = 8; RotateContinuous rotate_continuous = 9; + MoveContinuous move_continuous = 10; + TextureMoveContinuous texture_move_continuous = 11; } optional bool playing = 6; // default true (pause or running) @@ -52,6 +54,17 @@ message RotateContinuous { float speed = 2; } +message MoveContinuous { + decentraland.common.Vector3 direction = 1; + float speed = 2; +} + +message TextureMoveContinuous { + decentraland.common.Vector2 direction = 1; + float speed = 2; + optional TextureMovementType movement_type = 3; // default = TextureMovementType.TMT_OFFSET +} + enum TextureMovementType { TMT_OFFSET = 0; // default = TextureMovementType.TMT_OFFSET TMT_TILING = 1; diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index f0e22dcda..cbd2dee95 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -33,10 +33,14 @@ impl From for Tween { impl Tween { #[cfg(feature = "adr285")] fn is_continuous(&self) -> bool { - match self.0.mode { - Some(Mode::RotateContinuous(_)) => true, - _ => false, - } + matches!( + &self.0.mode, + Some( + Mode::RotateContinuous(_) + | Mode::MoveContinuous(_) + | Mode::TextureMoveContinuous(_) + ) + ) } fn apply( From 7ef9798c1eb2349eceae7b278944409c7d291e88 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 Jan 2026 18:01:10 -0300 Subject: [PATCH 10/35] Gizmos for `Move`, `Rotate`, and `Scale` --- crates/tween/src/tween_debug.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index ac9377a5a..f6ec217c4 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -306,6 +306,38 @@ fn plate_display_row( fn axis_gizmos(mut gizmos: Gizmos, tweens: Query<(&Tween, &GlobalTransform)>) { for (tween, global_transform) in tweens { match &tween.0.mode { + Some(Mode::Move(data)) => { + gizmos.arrow( + global_transform.translation(), + data.end.unwrap().world_vec_to_vec3(), + palettes::tailwind::RED_700, + ); + } + Some(Mode::Rotate(data)) => { + let start = global_transform.rotation(); + let end = data.end.unwrap().to_bevy_normalized(); + let angle = end.angle_between(start); + gizmos.arc_3d( + angle, + 5., + Isometry3d::from_translation(global_transform.translation()), + palettes::tailwind::RED_700, + ); + } + Some(Mode::Scale(data)) => { + let start = global_transform.scale().length().sqrt(); + let end = data.end.unwrap().world_vec_to_vec3().length().sqrt(); + gizmos.sphere( + Isometry3d::from_translation(global_transform.translation()), + start, + palettes::tailwind::RED_300, + ); + gizmos.sphere( + Isometry3d::from_translation(global_transform.translation()), + end, + palettes::tailwind::RED_700, + ); + } #[cfg(all(feature = "adr285", not(feature = "alt_rotate_continuous")))] Some(Mode::RotateContinuous(data)) => { let dcl_quat = data.direction.unwrap(); From 9e73ebe7fcd76a2e922dee32db27a315df3e03d4 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 21 Jan 2026 09:12:29 -0300 Subject: [PATCH 11/35] Remove wrong corrections from alternative rotate continuous --- crates/tween/src/lib.rs | 7 ++----- crates/tween/src/tween_debug.rs | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index cbd2dee95..e45c7065a 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -185,11 +185,8 @@ impl Tween { }; let dcl_quat = data.direction.unwrap(); let (axis, angle) = dcl_quat.to_bevy_normalized().to_axis_angle(); - let corrected_axis = Vec3::new(axis.x, -axis.z, -axis.y); - transform.rotation = Quat::from_axis_angle( - corrected_axis, - angle + startup_factor + post_startup, - ); + transform.rotation = + Quat::from_axis_angle(axis, angle + startup_factor + post_startup); } } _ => {} diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index f6ec217c4..805174d41 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -359,15 +359,13 @@ fn axis_gizmos(mut gizmos: Gizmos, tweens: Query<(&Tween, &GlobalTransform)>) { Some(Mode::RotateContinuous(data)) => { let direction = data.direction.unwrap(); let (axis, _) = direction.to_bevy_normalized().to_axis_angle(); - let correction = Quat::from_axis_angle(Vec3::X, -90.0f32.to_radians()); - let corrected_axis = Vec3::new(axis.x, -axis.z, -axis.y); gizmos.axes( - Isometry3d::new(global_transform.translation(), correction), + Isometry3d::from_translation(global_transform.translation()), 2.5, ); gizmos.arrow( global_transform.translation(), - global_transform.translation() + corrected_axis * 2.5, + global_transform.translation() + axis * 2.5, palettes::tailwind::RED_700, ); } From e0937638d8cd840d0098f24d8caec947a5610936 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 21 Jan 2026 12:02:54 -0300 Subject: [PATCH 12/35] Some refactor on `RotateContinuous` --- crates/tween/src/lib.rs | 55 ++++++++++++--------------------- crates/tween/src/tween_debug.rs | 34 ++++++++------------ 2 files changed, 33 insertions(+), 56 deletions(-) diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index e45c7065a..67267652a 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -1,7 +1,7 @@ #[cfg(feature = "tween_debug")] mod tween_debug; -#[cfg(all(feature = "adr285", not(feature = "alt_rotate_continuous")))] +#[cfg(feature = "adr285")] use std::f32::consts::FRAC_2_PI; use bevy::prelude::*; @@ -150,44 +150,29 @@ impl Tween { } #[cfg(feature = "adr285")] Some(Mode::RotateContinuous(data)) => { - #[cfg(not(feature = "alt_rotate_continuous"))] - { - // The rotation is desired. - // The speed and time is provided. - // The rotation is then calculated by integrating the - // speed function. - // The integral of a constant speed is `speed * time`. - let startup_factor = if self.0.duration > 0. { todo!() } else { 0. }; - let post_startup = if time > self.0.duration { - ((time - self.0.duration) / 1000.) * data.speed.to_radians() - } else { - 0. - }; + // The rotation is desired. + // The speed and time is provided. + // The rotation is then calculated by integrating the + // speed function. + // The integral of a constant speed is `speed * time`. + let startup_factor = if self.0.duration > 0. { todo!() } else { 0. }; + let post_startup = if time > self.0.duration { + ((time - self.0.duration) / 1000.) * data.speed.to_radians() + } else { + 0. + }; + let axis = if cfg!(feature = "alt_rotate_continuous") { + let dcl_quat = data.direction.unwrap(); + let (axis, _) = dcl_quat.to_bevy_normalized().to_axis_angle(); + axis + } else { let dcl_quat = data.direction.unwrap(); // +Z forward to Bevy's -Z forward let quat = dcl_quat.to_bevy_normalized() * Quat::from_axis_angle(Vec3::Y, FRAC_2_PI); - let axis = quat * Vec3::NEG_Y; - transform.rotation = Quat::from_axis_angle(axis, startup_factor + post_startup); - } - #[cfg(feature = "alt_rotate_continuous")] - { - // The rotation is desired. - // The speed and time is provided. - // The rotation is then calculated by integrating the - // speed function. - // The integral of a constant speed is `speed * time`. - let startup_factor = if self.0.duration > 0. { todo!() } else { 0. }; - let post_startup = if time > self.0.duration { - ((time - self.0.duration) / 1000.) * data.speed.to_radians() - } else { - 0. - }; - let dcl_quat = data.direction.unwrap(); - let (axis, angle) = dcl_quat.to_bevy_normalized().to_axis_angle(); - transform.rotation = - Quat::from_axis_angle(axis, angle + startup_factor + post_startup); - } + quat * Vec3::NEG_Y + }; + transform.rotation = Quat::from_axis_angle(axis, startup_factor + post_startup); } _ => {} } diff --git a/crates/tween/src/tween_debug.rs b/crates/tween/src/tween_debug.rs index 805174d41..392a094a4 100644 --- a/crates/tween/src/tween_debug.rs +++ b/crates/tween/src/tween_debug.rs @@ -1,4 +1,4 @@ -#[cfg(all(feature = "adr285", not(feature = "alt_rotate_continuous")))] +#[cfg(feature = "adr285")] use std::f32::consts::FRAC_2_PI; use std::fmt::Debug; @@ -338,31 +338,23 @@ fn axis_gizmos(mut gizmos: Gizmos, tweens: Query<(&Tween, &GlobalTransform)>) { palettes::tailwind::RED_700, ); } - #[cfg(all(feature = "adr285", not(feature = "alt_rotate_continuous")))] - Some(Mode::RotateContinuous(data)) => { - let dcl_quat = data.direction.unwrap(); - // +Z forward to Bevy's -Z forward - let quat = - dcl_quat.to_bevy_normalized() * Quat::from_axis_angle(Vec3::Y, FRAC_2_PI); - let axis = quat * Vec3::NEG_Y; - gizmos.axes( - Isometry3d::from_translation(global_transform.translation()), - 2.5, - ); - gizmos.arrow( - global_transform.translation(), - global_transform.translation() + axis * 2.5, - palettes::tailwind::RED_700, - ); - } - #[cfg(all(feature = "adr285", feature = "alt_rotate_continuous"))] + #[cfg(feature = "adr285")] Some(Mode::RotateContinuous(data)) => { - let direction = data.direction.unwrap(); - let (axis, _) = direction.to_bevy_normalized().to_axis_angle(); gizmos.axes( Isometry3d::from_translation(global_transform.translation()), 2.5, ); + let axis = if cfg!(feature = "alt_rotate_continuous") { + let direction = data.direction.unwrap(); + let (axis, _) = direction.to_bevy_normalized().to_axis_angle(); + axis + } else { + let dcl_quat = data.direction.unwrap(); + // +Z forward to Bevy's -Z forward + let quat = + dcl_quat.to_bevy_normalized() * Quat::from_axis_angle(Vec3::Y, FRAC_2_PI); + quat * Vec3::NEG_Y + }; gizmos.arrow( global_transform.translation(), global_transform.translation() + axis * 2.5, From 9b7f2fa9a19de3fcd3ce98668b8677dca9f27881 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Thu, 22 Jan 2026 09:35:18 -0300 Subject: [PATCH 13/35] Prepare systems to work on frame independent way for continuous tweens --- crates/tween/src/lib.rs | 131 +++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 29 deletions(-) diff --git a/crates/tween/src/lib.rs b/crates/tween/src/lib.rs index 67267652a..83dde7dae 100644 --- a/crates/tween/src/lib.rs +++ b/crates/tween/src/lib.rs @@ -182,6 +182,14 @@ impl Tween { #[derive(Component, Debug, PartialEq)] pub struct TweenState(PbTweenState); +/// This caches the initial [`Transform`] of a continuous tween +/// for calculating the new [`Transform`] in a frame independent +/// way +#[derive(Component, Deref)] +#[component(immutable)] +#[cfg(feature = "adr285")] +struct ContinuousTweenAnchor(Transform); + pub struct TweenPlugin; impl Plugin for TweenPlugin { @@ -193,39 +201,59 @@ impl Plugin for TweenPlugin { app.add_systems(Update, update_tween.in_set(SceneSets::PostLoop)); app.add_systems(PostUpdate, update_system_tween); + #[cfg(feature = "adr285")] + { + app.add_observer(tween_inserted); + app.add_observer(tween_replaced); + } + #[cfg(feature = "tween_debug")] app.add_plugins(tween_debug::TweenDebugPlugin); } } +#[cfg(not(feature = "adr285"))] +type TweenUpdateComponents<'a> = ( + Entity, + &'a ContainerEntity, + &'a ChildOf, + Ref<'a, Tween>, + Mut<'a, Transform>, + Option>, + Option<&'a MeshMaterial3d>, +); +#[cfg(feature = "adr285")] +type TweenUpdateComponents<'a> = ( + Entity, + &'a ContainerEntity, + &'a ChildOf, + Ref<'a, Tween>, + Mut<'a, Transform>, + Option>, + Option<&'a ContinuousTweenAnchor>, + Option<&'a MeshMaterial3d>, +); + #[allow(clippy::type_complexity)] -pub fn update_tween( +fn update_tween( mut commands: Commands, time: Res