From 21e9002e5f8d0e6edcac6b2f0fa5c2e10aa37d44 Mon Sep 17 00:00:00 2001 From: Annonnymmousss Date: Wed, 24 Dec 2025 06:17:03 +0530 Subject: [PATCH 1/4] fix : global coordinate on grabbing --- .../transform_layer_message_handler.rs | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 5c784b556e..1a63a86670 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -94,6 +94,8 @@ pub struct TransformLayerMessageHandler { // Path tool (ghost outlines showing pre-transform geometry) ghost_outline: Vec<(Vec, DAffine2)>, + + bounding_box_transform: DAffine2, } #[message_handler_data] @@ -232,9 +234,33 @@ impl MessageHandler> for match self.transform_operation { TransformOperation::None => (), TransformOperation::Grabbing(translation) => { - let translation_viewport = self.state.local_to_viewport_transform().matrix2 * translation.to_dvec(&self.state, document); + if !self.state.is_transforming_in_local_space { + let translation_viewport = translation.to_dvec(&self.state, document); + let pivot = document_to_viewport.transform_point2(self.grab_target); + let quad = Quad::from_box([pivot, pivot + translation_viewport]); + let scale = document_to_viewport.matrix2.y_axis.length(); + + responses.add(SelectToolMessage::PivotShift { + offset: Some(translation_viewport), + flush: false, + }); + + let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); + overlay_context.translation_box(translation_viewport / scale, quad, typed_string); + return; + } + + let local_to_viewport = self.state.local_to_viewport_transform(); + let translation_viewport = local_to_viewport.matrix2 * translation.to_dvec(&self.state, document); let pivot = document_to_viewport.transform_point2(self.grab_target); - let quad = Quad::from_box([pivot, pivot + translation_viewport]); + + let [local_x_axis, local_y_axis] = self.state.local_transform_axes; + let local_components = DVec2::new(translation_viewport.dot(local_x_axis), translation_viewport.dot(local_y_axis)); + let local_quad = Quad::from_box([DVec2::ZERO, local_components]); + let quad = DAffine2::from_translation(pivot) * local_to_viewport * local_quad; + + let scale = self.bounding_box_transform.matrix2.y_axis.length(); + let translation_for_display = local_components / scale; responses.add(SelectToolMessage::PivotShift { offset: Some(translation_viewport), @@ -242,7 +268,7 @@ impl MessageHandler> for }); let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); - overlay_context.translation_box(translation_viewport / document_to_viewport.matrix2.y_axis.length(), quad, typed_string); + overlay_context.translation_box(translation_for_display, quad, typed_string); } TransformOperation::Scaling(scale) => { let scale = scale.to_f64(self.state.is_rounded_to_intervals); @@ -349,7 +375,9 @@ impl MessageHandler> for }; self.layer_bounding_box = selected.bounding_box(); let bounding_box = select_tool::create_bounding_box_transform(document); + self.bounding_box_transform = bounding_box; self.state.local_transform_axes = [bounding_box.x_axis, bounding_box.y_axis].map(|axis| axis.normalize_or_zero()); + self.state.is_transforming_in_local_space = matches!(operation, TransformType::Grab) && self.state.local_transform_axes[0].dot(DVec2::X).abs() < 0.999; } TransformLayerMessage::BeginGrabPen { last_point, handle } | TransformLayerMessage::BeginRotatePen { last_point, handle } | TransformLayerMessage::BeginScalePen { last_point, handle } => { self.typing.clear(); @@ -537,10 +565,17 @@ impl MessageHandler> for match self.transform_operation { TransformOperation::None => {} TransformOperation::Grabbing(translation) => { - let delta_pos = input.mouse.position - self.mouse_position; - let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos); - let delta_viewport = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }; - let delta_scaled = delta_viewport / document_to_viewport.y_axis.length(); // Values are local to the viewport but scaled so values are relative to the current scale. + let viewport_delta = input.mouse.position - self.mouse_position; + let document_space_transform = self.initial_transform * document_to_viewport.inverse(); + let delta_pos = if self.state.is_transforming_in_local_space { + let [local_x_axis, local_y_axis] = self.state.local_transform_axes; + let local_components = DVec2::new(viewport_delta.dot(local_x_axis), viewport_delta.dot(local_y_axis)); + document_space_transform.matrix2 * local_components + } else { + document_space_transform.transform_vector2(viewport_delta) + }; + let scale = document_to_viewport.y_axis.length(); + let delta_scaled = (if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }) / scale; self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(delta_scaled)); self.transform_operation.apply_transform_operation(&mut selected, &self.state, document); } From 3893c9a1ea8ff10ed7c9b7c472cd85561530df88 Mon Sep 17 00:00:00 2001 From: Annonnymmousss Date: Wed, 24 Dec 2025 18:09:27 +0530 Subject: [PATCH 2/4] fix : local constraint --- .../transform_layer_message_handler.rs | 57 +++++-------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 1a63a86670..53fbe1bd3e 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -94,8 +94,6 @@ pub struct TransformLayerMessageHandler { // Path tool (ghost outlines showing pre-transform geometry) ghost_outline: Vec<(Vec, DAffine2)>, - - bounding_box_transform: DAffine2, } #[message_handler_data] @@ -234,33 +232,17 @@ impl MessageHandler> for match self.transform_operation { TransformOperation::None => (), TransformOperation::Grabbing(translation) => { - if !self.state.is_transforming_in_local_space { - let translation_viewport = translation.to_dvec(&self.state, document); - let pivot = document_to_viewport.transform_point2(self.grab_target); - let quad = Quad::from_box([pivot, pivot + translation_viewport]); - let scale = document_to_viewport.matrix2.y_axis.length(); - - responses.add(SelectToolMessage::PivotShift { - offset: Some(translation_viewport), - flush: false, - }); - - let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); - overlay_context.translation_box(translation_viewport / scale, quad, typed_string); - return; - } - - let local_to_viewport = self.state.local_to_viewport_transform(); - let translation_viewport = local_to_viewport.matrix2 * translation.to_dvec(&self.state, document); + let translation_viewport = self.state.local_to_viewport_transform().matrix2 * translation.to_dvec(&self.state, document); let pivot = document_to_viewport.transform_point2(self.grab_target); - - let [local_x_axis, local_y_axis] = self.state.local_transform_axes; - let local_components = DVec2::new(translation_viewport.dot(local_x_axis), translation_viewport.dot(local_y_axis)); - let local_quad = Quad::from_box([DVec2::ZERO, local_components]); - let quad = DAffine2::from_translation(pivot) * local_to_viewport * local_quad; - - let scale = self.bounding_box_transform.matrix2.y_axis.length(); - let translation_for_display = local_components / scale; + let quad = if self.state.local_transform_axes[0].dot(DVec2::X).abs() < 0.999 { + let [local_x_axis, local_y_axis] = self.state.local_transform_axes; + let local_components = DVec2::new(translation_viewport.dot(local_x_axis), translation_viewport.dot(local_y_axis)); + let local_quad = Quad::from_box([DVec2::ZERO, local_components]); + let local_to_viewport = DAffine2::from_cols(local_x_axis, local_y_axis, DVec2::ZERO); + DAffine2::from_translation(pivot) * local_to_viewport * local_quad + } else { + Quad::from_box([pivot, pivot + translation_viewport]) + }; responses.add(SelectToolMessage::PivotShift { offset: Some(translation_viewport), @@ -268,7 +250,7 @@ impl MessageHandler> for }); let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); - overlay_context.translation_box(translation_for_display, quad, typed_string); + overlay_context.translation_box(translation_viewport / document_to_viewport.matrix2.y_axis.length(), quad, typed_string); } TransformOperation::Scaling(scale) => { let scale = scale.to_f64(self.state.is_rounded_to_intervals); @@ -375,9 +357,7 @@ impl MessageHandler> for }; self.layer_bounding_box = selected.bounding_box(); let bounding_box = select_tool::create_bounding_box_transform(document); - self.bounding_box_transform = bounding_box; self.state.local_transform_axes = [bounding_box.x_axis, bounding_box.y_axis].map(|axis| axis.normalize_or_zero()); - self.state.is_transforming_in_local_space = matches!(operation, TransformType::Grab) && self.state.local_transform_axes[0].dot(DVec2::X).abs() < 0.999; } TransformLayerMessage::BeginGrabPen { last_point, handle } | TransformLayerMessage::BeginRotatePen { last_point, handle } | TransformLayerMessage::BeginScalePen { last_point, handle } => { self.typing.clear(); @@ -565,17 +545,10 @@ impl MessageHandler> for match self.transform_operation { TransformOperation::None => {} TransformOperation::Grabbing(translation) => { - let viewport_delta = input.mouse.position - self.mouse_position; - let document_space_transform = self.initial_transform * document_to_viewport.inverse(); - let delta_pos = if self.state.is_transforming_in_local_space { - let [local_x_axis, local_y_axis] = self.state.local_transform_axes; - let local_components = DVec2::new(viewport_delta.dot(local_x_axis), viewport_delta.dot(local_y_axis)); - document_space_transform.matrix2 * local_components - } else { - document_space_transform.transform_vector2(viewport_delta) - }; - let scale = document_to_viewport.y_axis.length(); - let delta_scaled = (if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }) / scale; + let delta_pos = input.mouse.position - self.mouse_position; + let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos); + let delta_viewport = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }; + let delta_scaled = delta_viewport / document_to_viewport.y_axis.length(); // Values are local to the viewport but scaled so values are relative to the current scale. self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(delta_scaled)); self.transform_operation.apply_transform_operation(&mut selected, &self.state, document); } From 0c7f091eadb1174f99ee1a43238e02338e9c04aa Mon Sep 17 00:00:00 2001 From: Annonnymmousss Date: Thu, 25 Dec 2025 18:40:59 +0530 Subject: [PATCH 3/4] fix : transformation to the local space --- .../transform_layer_message_handler.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 53fbe1bd3e..9d1bf4d1b4 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -237,9 +237,8 @@ impl MessageHandler> for let quad = if self.state.local_transform_axes[0].dot(DVec2::X).abs() < 0.999 { let [local_x_axis, local_y_axis] = self.state.local_transform_axes; let local_components = DVec2::new(translation_viewport.dot(local_x_axis), translation_viewport.dot(local_y_axis)); - let local_quad = Quad::from_box([DVec2::ZERO, local_components]); let local_to_viewport = DAffine2::from_cols(local_x_axis, local_y_axis, DVec2::ZERO); - DAffine2::from_translation(pivot) * local_to_viewport * local_quad + DAffine2::from_translation(pivot) * local_to_viewport * Quad::from_box([DVec2::ZERO, local_components]) } else { Quad::from_box([pivot, pivot + translation_viewport]) }; @@ -250,7 +249,10 @@ impl MessageHandler> for }); let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); - overlay_context.translation_box(translation_viewport / document_to_viewport.matrix2.y_axis.length(), quad, typed_string); + // A transformation to the local space of the transformation from the document + let document_to_local = glam::DMat2::from_cols(self.state.local_transform_axes[0], self.state.local_transform_axes[1]).inverse(); + let transform_local = document_to_local * document_to_viewport.matrix2.inverse() * translation_viewport; + overlay_context.translation_box(transform_local, quad, typed_string); } TransformOperation::Scaling(scale) => { let scale = scale.to_f64(self.state.is_rounded_to_intervals); @@ -279,8 +281,8 @@ impl MessageHandler> for } else if using_path_tool { start_mouse - self.state.pivot_viewport(document) } else { - // TODO: This is always zero breaking the `.to_angle()` below? - self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right() + // Use local_transform_axes to preserve rotation across chained operations + self.state.local_transform_axes[0] }; let tilt_offset = document.document_ptz.unmodified_tilt(); let offset_angle = offset_angle.to_angle() + tilt_offset; From 49b9cd821024d0ccf7ca75cbf97f680e86fb7bbd Mon Sep 17 00:00:00 2001 From: Annonnymmousss Date: Thu, 25 Dec 2025 22:52:17 +0530 Subject: [PATCH 4/4] fix : local_transform_axes --- .../tool/transform_layer/transform_layer_message_handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 9d1bf4d1b4..2841241467 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -250,8 +250,8 @@ impl MessageHandler> for let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); // A transformation to the local space of the transformation from the document - let document_to_local = glam::DMat2::from_cols(self.state.local_transform_axes[0], self.state.local_transform_axes[1]).inverse(); - let transform_local = document_to_local * document_to_viewport.matrix2.inverse() * translation_viewport; + let viewport_to_local = glam::DMat2::from_cols(self.state.local_transform_axes[0], self.state.local_transform_axes[1]).inverse(); + let transform_local = viewport_to_local * translation_viewport / document_to_viewport.matrix2.y_axis.length(); overlay_context.translation_box(transform_local, quad, typed_string); } TransformOperation::Scaling(scale) => {