use crate::editor::context::{self, EditorContext};
use crate::editor::gizmo::drag_math::{
BASE_SENSITIVITY, MIN_ENTITY_SCALE, compute_drag_sensitivity, world_to_local_movement,
};
use crate::editor::input::is_ctrl_pressed;
use crate::prelude::*;
use nalgebra_glm::{Vec2, Vec3};
fn constraint_to_grab_vector(constraint: context::TransformConstraint, value: Vec3) -> Vec3 {
match constraint {
context::TransformConstraint::None => value,
context::TransformConstraint::X => Vec3::new(value.x, 0.0, 0.0),
context::TransformConstraint::Y => Vec3::new(0.0, value.y, 0.0),
context::TransformConstraint::Z => Vec3::new(0.0, 0.0, value.z),
}
}
fn constraint_to_rotation_axis(constraint: context::TransformConstraint) -> Vec3 {
match constraint {
context::TransformConstraint::None | context::TransformConstraint::Y => Vec3::y(),
context::TransformConstraint::X => Vec3::x(),
context::TransformConstraint::Z => Vec3::z(),
}
}
fn constraint_to_scale_delta(constraint: context::TransformConstraint, scale_factor: f32) -> Vec3 {
match constraint {
context::TransformConstraint::None => Vec3::new(scale_factor, scale_factor, scale_factor),
context::TransformConstraint::X => Vec3::new(scale_factor, 1.0, 1.0),
context::TransformConstraint::Y => Vec3::new(1.0, scale_factor, 1.0),
context::TransformConstraint::Z => Vec3::new(1.0, 1.0, scale_factor),
}
}
pub fn start_modal_transform(
context: &mut EditorContext,
world: &mut World,
operation: context::TransformOperation,
) {
if context.selection.is_empty() {
return;
}
if context.selection.primary().is_none() {
return;
}
context.transform_edit.modal.operation = operation;
context.transform_edit.modal.constraint = context::TransformConstraint::None;
context.transform_edit.modal.start_mouse_pos = world.resources.input.mouse.position;
context.transform_edit.modal.accumulated_delta = Vec2::zeros();
context.transform_edit.modal.numeric_input.clear();
context.transform_edit.modal.numeric_value = None;
context.transform_edit.modal.initial_transforms = context.capture_selection_transforms(world);
context.begin_selection_transform_tracking(world);
}
pub fn confirm_modal_transform(context: &mut EditorContext, world: &mut World) -> bool {
let initial_transforms = std::mem::take(&mut context.transform_edit.modal.initial_transforms);
let modified =
context.commit_selection_transforms(world, initial_transforms, "Transform selection");
context.transform_edit.modal = context::ModalTransformState::default();
modified
}
pub fn cancel_modal_transform(context: &mut EditorContext, world: &mut World) {
for (entity, transform) in &context.transform_edit.modal.initial_transforms {
world.set_local_transform(*entity, *transform);
mark_local_transform_dirty(world, *entity);
}
context.transform_edit.modal = context::ModalTransformState::default();
}
pub fn update_modal_transform(context: &mut EditorContext, world: &mut World) -> bool {
if context.transform_edit.modal.operation == context::TransformOperation::None {
return false;
}
if context.transform_edit.modal.numeric_value.is_some() {
return false;
}
if context.transform_edit.modal.initial_transforms.is_empty() {
context.transform_edit.modal = context::ModalTransformState::default();
return false;
}
let mouse = &world.resources.input.mouse;
let mouse_delta = mouse.position - context.transform_edit.modal.start_mouse_pos;
if mouse
.state
.contains(crate::prelude::MouseState::LEFT_JUST_PRESSED)
{
return confirm_modal_transform(context, world);
}
if mouse
.state
.contains(crate::prelude::MouseState::RIGHT_JUST_PRESSED)
{
cancel_modal_transform(context, world);
return false;
}
let constraint = context.transform_edit.modal.constraint;
let operation = context.transform_edit.modal.operation;
let ctrl_pressed = is_ctrl_pressed(world);
let Some(camera_entity) = world.resources.active_camera else {
return false;
};
let Some(camera_transform) = world.get_global_transform(camera_entity) else {
return false;
};
let (camera_right, camera_up, _) = super::drag_math::extract_normalized_axes(camera_transform);
for (&entity, &initial) in &context.transform_edit.modal.initial_transforms {
match operation {
context::TransformOperation::Grab => {
let sensitivity =
compute_drag_sensitivity(context.gizmo_interaction.camera_distance);
let mut world_movement = camera_right * mouse_delta.x * sensitivity
- camera_up * mouse_delta.y * sensitivity;
if ctrl_pressed {
world_movement.x = context.snap_settings.snap_translation(world_movement.x);
world_movement.y = context.snap_settings.snap_translation(world_movement.y);
world_movement.z = context.snap_settings.snap_translation(world_movement.z);
}
let constrained_movement = constraint_to_grab_vector(constraint, world_movement);
let local_movement = world_to_local_movement(world, entity, constrained_movement);
let mut new_transform = initial;
new_transform.translation += local_movement;
world.set_local_transform(entity, new_transform);
mark_local_transform_dirty(world, entity);
}
context::TransformOperation::Rotate => {
let sensitivity = BASE_SENSITIVITY;
let mut angle = mouse_delta.x * sensitivity;
if ctrl_pressed {
let degrees = angle.to_degrees();
let snapped_degrees = context.snap_settings.snap_rotation(degrees);
angle = snapped_degrees.to_radians();
}
let axis = constraint_to_rotation_axis(constraint);
let rotation = nalgebra_glm::quat_angle_axis(angle, &axis);
let mut new_transform = initial;
new_transform.rotation = rotation * initial.rotation;
world.set_local_transform(entity, new_transform);
mark_local_transform_dirty(world, entity);
}
context::TransformOperation::Scale => {
let sensitivity = BASE_SENSITIVITY;
let mut scale_factor = 1.0 + mouse_delta.x * sensitivity;
scale_factor = scale_factor.max(MIN_ENTITY_SCALE);
if ctrl_pressed {
scale_factor = context.snap_settings.snap_scale(scale_factor);
}
let scale_delta = constraint_to_scale_delta(constraint, scale_factor);
let mut new_transform = initial;
new_transform.scale = Vec3::new(
initial.scale.x * scale_delta.x,
initial.scale.y * scale_delta.y,
initial.scale.z * scale_delta.z,
);
world.set_local_transform(entity, new_transform);
mark_local_transform_dirty(world, entity);
}
context::TransformOperation::None => {}
}
}
false
}
pub fn handle_numeric_input_for_transform(
context: &mut EditorContext,
key_code: KeyCode,
world: &mut World,
) -> bool {
let char_opt = match key_code {
KeyCode::Digit0 | KeyCode::Numpad0 => Some('0'),
KeyCode::Digit1 | KeyCode::Numpad1 => Some('1'),
KeyCode::Digit2 | KeyCode::Numpad2 => Some('2'),
KeyCode::Digit3 | KeyCode::Numpad3 => Some('3'),
KeyCode::Digit4 | KeyCode::Numpad4 => Some('4'),
KeyCode::Digit5 | KeyCode::Numpad5 => Some('5'),
KeyCode::Digit6 | KeyCode::Numpad6 => Some('6'),
KeyCode::Digit7 | KeyCode::Numpad7 => Some('7'),
KeyCode::Digit8 | KeyCode::Numpad8 => Some('8'),
KeyCode::Digit9 | KeyCode::Numpad9 => Some('9'),
KeyCode::Period | KeyCode::NumpadDecimal => Some('.'),
KeyCode::Minus | KeyCode::NumpadSubtract => Some('-'),
KeyCode::Backspace => {
context.transform_edit.modal.numeric_input.pop();
context.transform_edit.modal.numeric_value =
context.transform_edit.modal.numeric_input.parse().ok();
return false;
}
KeyCode::Enter | KeyCode::NumpadEnter => {
apply_numeric_transform(context, world);
return confirm_modal_transform(context, world);
}
_ => None,
};
if let Some(character) = char_opt {
if character == '-' && !context.transform_edit.modal.numeric_input.is_empty() {
return false;
}
if character == '.' && context.transform_edit.modal.numeric_input.contains('.') {
return false;
}
context.transform_edit.modal.numeric_input.push(character);
context.transform_edit.modal.numeric_value =
context.transform_edit.modal.numeric_input.parse().ok();
if context.transform_edit.modal.numeric_value.is_some() {
apply_numeric_transform(context, world);
}
}
false
}
fn apply_numeric_transform(context: &mut EditorContext, world: &mut World) {
let Some(value) = context.transform_edit.modal.numeric_value else {
return;
};
let operation = context.transform_edit.modal.operation;
let constraint = context.transform_edit.modal.constraint;
for (entity, initial) in &context.transform_edit.modal.initial_transforms {
let mut new_transform = *initial;
match operation {
context::TransformOperation::Grab => {
let movement =
constraint_to_grab_vector(constraint, Vec3::new(value, value, value));
new_transform.translation = initial.translation + movement;
}
context::TransformOperation::Rotate => {
let angle = value.to_radians();
let axis = constraint_to_rotation_axis(constraint);
let rotation = nalgebra_glm::quat_angle_axis(angle, &axis);
new_transform.rotation = rotation * initial.rotation;
}
context::TransformOperation::Scale => {
let scale_factor = value.max(MIN_ENTITY_SCALE);
let scale_delta = constraint_to_scale_delta(constraint, scale_factor);
new_transform.scale = Vec3::new(
initial.scale.x * scale_delta.x,
initial.scale.y * scale_delta.y,
initial.scale.z * scale_delta.z,
);
}
context::TransformOperation::None => {}
}
world.set_local_transform(*entity, new_transform);
mark_local_transform_dirty(world, *entity);
}
}