use crate::ecs::gizmos::GizmoMode;
use crate::prelude::*;
use nalgebra_glm::{Vec2, Vec3};
pub const MIN_ENTITY_SCALE: f32 = 0.01;
pub const BASE_SENSITIVITY: f32 = 0.01;
const PLANE_AXIS_THRESHOLD: f32 = 1.05;
const SINGLE_AXIS_THRESHOLD: f32 = 0.99;
pub fn compute_drag_sensitivity(camera_distance: f32) -> f32 {
BASE_SENSITIVITY * (camera_distance * 0.1).sqrt().max(0.5)
}
pub struct DragParams {
pub mode: GizmoMode,
pub camera_right: Vec3,
pub camera_up: Vec3,
pub mouse_delta: Vec2,
pub sensitivity: f32,
}
pub fn determine_drag_mode(axis: Vec3) -> crate::ecs::gizmos::GizmoDragMode {
use crate::ecs::gizmos::GizmoDragMode;
if axis.x > PLANE_AXIS_THRESHOLD
|| axis.y > PLANE_AXIS_THRESHOLD
|| axis.z > PLANE_AXIS_THRESHOLD
{
let plane_normal = if axis.x > PLANE_AXIS_THRESHOLD && axis.y > PLANE_AXIS_THRESHOLD {
nalgebra_glm::vec3(0.0, 0.0, 1.0)
} else if axis.y > PLANE_AXIS_THRESHOLD && axis.z > PLANE_AXIS_THRESHOLD {
nalgebra_glm::vec3(1.0, 0.0, 0.0)
} else if axis.x > PLANE_AXIS_THRESHOLD && axis.z > PLANE_AXIS_THRESHOLD {
nalgebra_glm::vec3(0.0, 1.0, 0.0)
} else {
return GizmoDragMode::Free;
};
GizmoDragMode::Plane(plane_normal)
} else {
let single_axis = if axis.x > SINGLE_AXIS_THRESHOLD {
nalgebra_glm::vec3(1.0, 0.0, 0.0)
} else if axis.y > SINGLE_AXIS_THRESHOLD {
nalgebra_glm::vec3(0.0, 1.0, 0.0)
} else if axis.z > SINGLE_AXIS_THRESHOLD {
nalgebra_glm::vec3(0.0, 0.0, 1.0)
} else {
return GizmoDragMode::Free;
};
GizmoDragMode::Axis(single_axis)
}
}
pub fn extract_normalized_axes(transform: &GlobalTransform) -> (Vec3, Vec3, Vec3) {
let right = nalgebra_glm::normalize(&nalgebra_glm::vec3(
transform.0[(0, 0)],
transform.0[(1, 0)],
transform.0[(2, 0)],
));
let up = nalgebra_glm::normalize(&nalgebra_glm::vec3(
transform.0[(0, 1)],
transform.0[(1, 1)],
transform.0[(2, 1)],
));
let forward = nalgebra_glm::normalize(&nalgebra_glm::vec3(
transform.0[(0, 2)],
transform.0[(1, 2)],
transform.0[(2, 2)],
));
(right, up, forward)
}
pub fn rotate_vec3_by_global_transform(transform: &GlobalTransform, v: &Vec3) -> Vec3 {
let (right, up, forward) = extract_normalized_axes(transform);
right * v.x + up * v.y + forward * v.z
}
pub fn rotate_vec3_by_inverse_global_transform(transform: &GlobalTransform, v: &Vec3) -> Vec3 {
let (right, up, forward) = extract_normalized_axes(transform);
nalgebra_glm::vec3(
nalgebra_glm::dot(&right, v),
nalgebra_glm::dot(&up, v),
nalgebra_glm::dot(&forward, v),
)
}
pub fn world_to_local_movement(world: &World, entity: Entity, world_movement: Vec3) -> Vec3 {
if let Some(parent_entity) = world.get_parent(entity).and_then(|p| p.0) {
if let Some(parent_transform) = world.get_global_transform(parent_entity) {
rotate_vec3_by_inverse_global_transform(parent_transform, &world_movement)
} else {
world_movement
}
} else {
world_movement
}
}
enum TransformDelta {
Translation(Vec3),
Rotation(Quat),
Scale(Vec3),
}
fn apply_transform_delta(world: &mut World, target: Entity, delta: TransformDelta) {
let Some(transform) = world.get_local_transform(target) else {
return;
};
let mut new_transform = *transform;
match delta {
TransformDelta::Translation(movement) => {
new_transform.translation += movement;
}
TransformDelta::Rotation(rotation) => {
new_transform.rotation = rotation * new_transform.rotation;
}
TransformDelta::Scale(scale) => {
new_transform.scale.x *= scale.x;
new_transform.scale.y *= scale.y;
new_transform.scale.z *= scale.z;
new_transform.scale.x = new_transform.scale.x.max(MIN_ENTITY_SCALE);
new_transform.scale.y = new_transform.scale.y.max(MIN_ENTITY_SCALE);
new_transform.scale.z = new_transform.scale.z.max(MIN_ENTITY_SCALE);
}
}
world.assign_local_transform(target, new_transform);
}
fn compute_translation_on_axis(
world: &World,
target: Entity,
axis: Vec3,
params: &DragParams,
) -> Vec3 {
let world_axis = if let Some(entity_transform) = world.get_global_transform(target) {
rotate_vec3_by_global_transform(entity_transform, &axis)
} else {
axis
};
let screen_movement =
params.camera_right * params.mouse_delta.x - params.camera_up * params.mouse_delta.y;
let movement_amount = nalgebra_glm::dot(&screen_movement, &world_axis) * params.sensitivity;
let world_movement = world_axis * movement_amount;
world_to_local_movement(world, target, world_movement)
}
fn compute_translation_on_plane(
world: &World,
target: Entity,
plane_normal: Vec3,
params: &DragParams,
) -> Vec3 {
let world_plane_normal = if let Some(entity_transform) = world.get_global_transform(target) {
rotate_vec3_by_global_transform(entity_transform, &plane_normal)
} else {
plane_normal
};
let screen_movement = params.camera_right * params.mouse_delta.x * params.sensitivity
- params.camera_up * params.mouse_delta.y * params.sensitivity;
let world_movement = screen_movement
- world_plane_normal * nalgebra_glm::dot(&screen_movement, &world_plane_normal);
world_to_local_movement(world, target, world_movement)
}
fn compute_free_translation(world: &World, target: Entity, params: &DragParams) -> Vec3 {
let world_movement = params.camera_right * params.mouse_delta.x * params.sensitivity
- params.camera_up * params.mouse_delta.y * params.sensitivity;
world_to_local_movement(world, target, world_movement)
}
fn rotation_angle_for_axis(axis: Vec3, mouse_delta: Vec2, rotation_speed: f32) -> f32 {
if axis.x > SINGLE_AXIS_THRESHOLD {
-mouse_delta.y * rotation_speed
} else if axis.y > SINGLE_AXIS_THRESHOLD {
mouse_delta.x * rotation_speed
} else {
-mouse_delta.x * rotation_speed
}
}
fn rotation_angle_for_plane(plane_normal: Vec3, mouse_delta: Vec2, rotation_speed: f32) -> f32 {
if plane_normal.z.abs() > SINGLE_AXIS_THRESHOLD {
mouse_delta.x * rotation_speed
} else if plane_normal.x.abs() > SINGLE_AXIS_THRESHOLD {
-mouse_delta.y * rotation_speed
} else {
mouse_delta.x * rotation_speed
}
}
fn scale_factors_for_axis(axis: Vec3, scale_delta: f32) -> Vec3 {
let factor = 1.0 + scale_delta;
if axis.x > SINGLE_AXIS_THRESHOLD {
Vec3::new(factor, 1.0, 1.0)
} else if axis.y > SINGLE_AXIS_THRESHOLD {
Vec3::new(1.0, factor, 1.0)
} else {
Vec3::new(1.0, 1.0, factor)
}
}
fn scale_factors_for_plane(plane_normal: Vec3, scale_delta: f32) -> Vec3 {
let factor = 1.0 + scale_delta;
if plane_normal.z.abs() > SINGLE_AXIS_THRESHOLD {
Vec3::new(factor, factor, 1.0)
} else if plane_normal.x.abs() > SINGLE_AXIS_THRESHOLD {
Vec3::new(1.0, factor, factor)
} else {
Vec3::new(factor, 1.0, factor)
}
}
pub fn apply_axis_drag(world: &mut World, target: Entity, axis: Vec3, params: &DragParams) {
let delta = match params.mode {
GizmoMode::LocalTranslation | GizmoMode::GlobalTranslation => {
TransformDelta::Translation(compute_translation_on_axis(world, target, axis, params))
}
GizmoMode::Rotation => {
let rotation_speed = params.sensitivity * 2.0;
let angle = rotation_angle_for_axis(axis, params.mouse_delta, rotation_speed);
TransformDelta::Rotation(nalgebra_glm::quat_angle_axis(angle, &axis))
}
GizmoMode::Scale => {
let scale_delta = (params.mouse_delta.x - params.mouse_delta.y) * params.sensitivity;
TransformDelta::Scale(scale_factors_for_axis(axis, scale_delta))
}
};
apply_transform_delta(world, target, delta);
}
pub fn apply_plane_drag(
world: &mut World,
target: Entity,
plane_normal: Vec3,
params: &DragParams,
) {
let delta = match params.mode {
GizmoMode::LocalTranslation | GizmoMode::GlobalTranslation => TransformDelta::Translation(
compute_translation_on_plane(world, target, plane_normal, params),
),
GizmoMode::Rotation => {
let rotation_speed = params.sensitivity * 2.0;
let angle = rotation_angle_for_plane(plane_normal, params.mouse_delta, rotation_speed);
TransformDelta::Rotation(nalgebra_glm::quat_angle_axis(angle, &plane_normal))
}
GizmoMode::Scale => {
let scale_delta = (params.mouse_delta.x - params.mouse_delta.y) * params.sensitivity;
TransformDelta::Scale(scale_factors_for_plane(plane_normal, scale_delta))
}
};
apply_transform_delta(world, target, delta);
}
pub fn apply_free_drag(world: &mut World, target: Entity, params: &DragParams) {
let delta = match params.mode {
GizmoMode::LocalTranslation | GizmoMode::GlobalTranslation => {
TransformDelta::Translation(compute_free_translation(world, target, params))
}
GizmoMode::Rotation => {
let rotation_speed = params.sensitivity * 2.0;
let axis_x = nalgebra_glm::vec3(1.0, 0.0, 0.0);
let axis_y = nalgebra_glm::vec3(0.0, 1.0, 0.0);
let rotation_x =
nalgebra_glm::quat_angle_axis(-params.mouse_delta.y * rotation_speed, &axis_x);
let rotation_y =
nalgebra_glm::quat_angle_axis(params.mouse_delta.x * rotation_speed, &axis_y);
TransformDelta::Rotation(rotation_y * rotation_x)
}
GizmoMode::Scale => {
let scale_delta = (params.mouse_delta.x - params.mouse_delta.y) * params.sensitivity;
let factor = 1.0 + scale_delta;
TransformDelta::Scale(Vec3::new(factor, factor, factor))
}
};
apply_transform_delta(world, target, delta);
}