use crate::math::{DVec3, intersect_plane, ray_to_ray, round_to_interval};
use crate::subgizmo::common::{
draw_arrow, draw_circle, draw_plane, gizmo_color, gizmo_normal, inner_circle_radius,
pick_arrow, pick_circle, pick_plane, plane_bitangent, plane_global_origin, plane_tangent,
};
use crate::subgizmo::{SubGizmoConfig, SubGizmoKind, common::TransformKind};
use crate::{GizmoDirection, GizmoDrawData, GizmoMode, GizmoOrientation, GizmoResult, gizmo::Ray};
use super::common::PickResult;
pub(crate) type TranslationSubGizmo = SubGizmoConfig<Translation>;
#[derive(Debug, Copy, Clone, Hash)]
pub(crate) struct TranslationParams {
pub mode: GizmoMode,
pub direction: GizmoDirection,
pub transform_kind: TransformKind,
}
#[derive(Default, Debug, Copy, Clone)]
pub(crate) struct TranslationState {
start_view_dir: DVec3,
start_point: DVec3,
last_point: DVec3,
current_delta: DVec3,
}
#[derive(Default, Debug, Copy, Clone)]
pub(crate) struct Translation;
impl SubGizmoKind for Translation {
type Params = TranslationParams;
type State = TranslationState;
type PickPreview = PickResult;
fn pick_preview(subgizmo: &TranslationSubGizmo, ray: Ray) -> PickResult {
match (subgizmo.transform_kind, subgizmo.direction) {
(TransformKind::Plane, GizmoDirection::View) => pick_circle(
&subgizmo.config,
ray,
inner_circle_radius(&subgizmo.config),
true,
),
(TransformKind::Plane, _) => pick_plane(&subgizmo.config, ray, subgizmo.direction),
(TransformKind::Axis, _) => {
pick_arrow(&subgizmo.config, ray, subgizmo.direction, subgizmo.mode)
}
}
}
fn pick(subgizmo: &mut TranslationSubGizmo, ray: Ray) -> Option<f64> {
let pick_result = Self::pick_preview(subgizmo, ray);
subgizmo.state.start_view_dir = subgizmo.config.view_forward();
subgizmo.state.start_point = pick_result.subgizmo_point;
subgizmo.state.last_point = pick_result.subgizmo_point;
subgizmo.state.current_delta = DVec3::ZERO;
if pick_result.picked {
Some(pick_result.t)
} else {
None
}
}
fn update(subgizmo: &mut TranslationSubGizmo, ray: Ray) -> Option<GizmoResult> {
if subgizmo.config.view_forward() != subgizmo.state.start_view_dir {
Self::pick(subgizmo, ray);
}
let mut new_point = if subgizmo.transform_kind == TransformKind::Axis {
point_on_axis(subgizmo, ray)
} else {
point_on_plane(
gizmo_normal(&subgizmo.config, subgizmo.direction),
plane_global_origin(&subgizmo.config, subgizmo.direction),
ray,
)?
};
let mut new_delta = new_point - subgizmo.state.start_point;
if subgizmo.config.snapping {
new_delta = if subgizmo.transform_kind == TransformKind::Axis {
snap_translation_vector(subgizmo, new_delta)
} else {
snap_translation_plane(subgizmo, new_delta)
};
new_point = subgizmo.state.start_point + new_delta;
}
let mut translation_delta = new_point - subgizmo.state.last_point;
let mut total_translation = new_point - subgizmo.state.start_point;
if subgizmo.config.orientation() == GizmoOrientation::Local {
let inverse_rotation = subgizmo.config.rotation.inverse();
translation_delta = inverse_rotation * translation_delta;
total_translation = inverse_rotation * total_translation;
}
subgizmo.state.last_point = new_point;
subgizmo.state.current_delta = new_delta;
Some(GizmoResult::Translation {
delta: translation_delta.into(),
total: total_translation.into(),
})
}
fn draw(subgizmo: &TranslationSubGizmo) -> GizmoDrawData {
match (subgizmo.transform_kind, subgizmo.direction) {
(TransformKind::Axis, _) => draw_arrow(
&subgizmo.config,
subgizmo.focused,
subgizmo.direction,
subgizmo.mode,
),
(TransformKind::Plane, GizmoDirection::View) => draw_circle(
&subgizmo.config,
gizmo_color(&subgizmo.config, subgizmo.focused, subgizmo.direction),
inner_circle_radius(&subgizmo.config),
false,
),
(TransformKind::Plane, _) => {
draw_plane(&subgizmo.config, subgizmo.focused, subgizmo.direction)
}
}
}
}
fn point_on_axis(subgizmo: &SubGizmoConfig<Translation>, ray: Ray) -> DVec3 {
let origin = subgizmo.config.translation;
let direction = gizmo_normal(&subgizmo.config, subgizmo.direction);
let (_ray_t, subgizmo_t) = ray_to_ray(ray.origin, ray.direction, origin, direction);
origin + direction * subgizmo_t
}
fn point_on_plane(plane_normal: DVec3, plane_origin: DVec3, ray: Ray) -> Option<DVec3> {
let mut t = 0.0;
if !intersect_plane(
plane_normal,
plane_origin,
ray.origin,
ray.direction,
&mut t,
) {
None
} else {
Some(ray.origin + ray.direction * t)
}
}
fn snap_translation_vector(subgizmo: &SubGizmoConfig<Translation>, new_delta: DVec3) -> DVec3 {
let delta_length = new_delta.length();
if delta_length > 1e-5 {
new_delta / delta_length
* round_to_interval(delta_length, subgizmo.config.snap_distance as f64)
} else {
new_delta
}
}
fn snap_translation_plane(subgizmo: &SubGizmoConfig<Translation>, new_delta: DVec3) -> DVec3 {
let mut bitangent = plane_bitangent(subgizmo.direction);
let mut tangent = plane_tangent(subgizmo.direction);
if subgizmo.config.local_space() {
bitangent = subgizmo.config.rotation * bitangent;
tangent = subgizmo.config.rotation * tangent;
}
let cb = new_delta.cross(-bitangent);
let ct = new_delta.cross(tangent);
let lb = cb.length();
let lt = ct.length();
let n = gizmo_normal(&subgizmo.config, subgizmo.direction);
if lb > 1e-5 && lt > 1e-5 {
bitangent * round_to_interval(lt, subgizmo.config.snap_distance as f64) * (ct / lt).dot(n)
+ tangent
* round_to_interval(lb, subgizmo.config.snap_distance as f64)
* (cb / lb).dot(n)
} else {
new_delta
}
}