use crate::ecs::{EditorWorld, PushPullSession};
use crate::systems::input;
use nightshade::ecs::input::resources::MouseState;
use nightshade::prelude::*;
pub fn is_dragging(editor_world: &EditorWorld) -> bool {
editor_world.resources.push_pull.session.is_some()
}
pub fn tick(editor_world: &mut EditorWorld, world: &mut World) {
if !editor_world.resources.push_pull.active {
if editor_world.resources.push_pull.session.is_some() {
cancel_session(editor_world, world);
}
return;
}
let mouse = *nightshade::ecs::input::access::mouse_for_active(world);
let in_viewport = input::mouse_in_active_viewport(world);
let ui_block = input::ui_capturing(world);
let left_pressed = mouse.state.contains(MouseState::LEFT_CLICKED);
let just_pressed = mouse.state.contains(MouseState::LEFT_JUST_PRESSED);
let just_released = mouse.state.contains(MouseState::LEFT_JUST_RELEASED);
let escape = world
.resources
.input
.keyboard
.is_key_pressed(KeyCode::Escape);
if escape {
cancel_session(editor_world, world);
return;
}
if editor_world.resources.push_pull.session.is_none() {
if just_pressed && in_viewport && !ui_block {
start_session(editor_world, world, mouse.position);
} else if in_viewport && !ui_block {
highlight_face(editor_world, world, mouse.position);
} else {
clear_highlight(world);
}
return;
}
apply_drag(editor_world, world, mouse.position);
if just_released || !left_pressed {
commit_session(editor_world, world);
}
}
fn picked_face(
editor_world: &EditorWorld,
world: &World,
mouse_position: Vec2,
) -> Option<PickingResult> {
pick_entities(world, mouse_position, PickingOptions::default())
.into_iter()
.find(|result| {
!editor_world
.resources
.editor_scene
.is_scaffolding(result.entity)
})
}
fn highlight_face(editor_world: &EditorWorld, world: &mut World, mouse_position: Vec2) {
match picked_face(editor_world, world, mouse_position) {
Some(result) => {
world
.resources
.editor_selection
.bounding_volume_selected_entity = Some(result.entity);
world.resources.editor_selection.selected_entities.clear();
}
None => clear_highlight(world),
}
}
fn clear_highlight(world: &mut World) {
world
.resources
.editor_selection
.bounding_volume_selected_entity = None;
world.resources.editor_selection.selected_entities.clear();
}
fn camera_forward(world: &World) -> Vec3 {
world
.resources
.active_camera
.and_then(|camera| world.core.get_global_transform(camera))
.map(|transform| transform.forward_vector())
.unwrap_or_else(|| Vec3::new(0.0, 0.0, -1.0))
}
fn start_session(editor_world: &mut EditorWorld, world: &mut World, mouse_position: Vec2) {
let Some(result) = picked_face(editor_world, world, mouse_position) else {
return;
};
let entity = result.entity;
let Some(bounding_volume) = world.core.get_bounding_volume(entity) else {
return;
};
let Some(global) = world.core.get_global_transform(entity) else {
return;
};
let obb = bounding_volume.transform(&global.0).obb;
let inverse = obb.orientation.conjugate();
let local = nalgebra_glm::quat_rotate_vec3(&inverse, &(result.world_position - obb.center));
let ratio = Vec3::new(
local.x / obb.half_extents.x.max(1.0e-6),
local.y / obb.half_extents.y.max(1.0e-6),
local.z / obb.half_extents.z.max(1.0e-6),
);
let axis = if ratio.x.abs() >= ratio.y.abs() && ratio.x.abs() >= ratio.z.abs() {
0
} else if ratio.y.abs() >= ratio.z.abs() {
1
} else {
2
};
let sign = if ratio[axis] >= 0.0 { 1.0 } else { -1.0 };
let Some(local_transform) = world.core.get_local_transform(entity).copied() else {
return;
};
let scale_axis = local_transform.scale[axis];
let unit_half = if scale_axis.abs() > 1.0e-6 {
obb.half_extents[axis] / scale_axis
} else {
0.5
};
let center = obb.center[axis];
let half = obb.half_extents[axis];
let Some(uuid) = editor_world.resources.editor_scene.uuid_for(entity) else {
return;
};
editor_world.resources.push_pull.session = Some(PushPullSession {
entity,
uuid,
axis,
sign,
fixed_face: center - sign * half,
unit_half,
plane_point: result.world_position,
plane_normal: camera_forward(world),
start_face: center + sign * half,
original_local: local_transform,
});
world
.resources
.editor_selection
.bounding_volume_selected_entity = Some(entity);
world.resources.editor_selection.selected_entities.clear();
}
fn apply_drag(editor_world: &mut EditorWorld, world: &mut World, mouse_position: Vec2) {
let Some(session) = editor_world.resources.push_pull.session.clone() else {
return;
};
let Some(ray) = PickingRay::from_screen_position(world, mouse_position) else {
return;
};
let plane_distance = -nalgebra_glm::dot(&session.plane_normal, &session.plane_point);
let Some(hit) = ray.intersect_plane(session.plane_normal, plane_distance) else {
return;
};
let mut axis_direction = Vec3::zeros();
axis_direction[session.axis] = session.sign;
let delta = nalgebra_glm::dot(&(hit - session.plane_point), &axis_direction);
let step = super::retained_ui::build::snap_step(editor_world);
let new_face = snap_scalar(session.start_face + session.sign * delta, step);
let size = ((new_face - session.fixed_face) * session.sign).max(step);
let new_center = session.fixed_face + session.sign * size * 0.5;
let new_scale = size * 0.5 / session.unit_half.max(1.0e-6);
if let Some(transform) = world.core.get_local_transform_mut(session.entity) {
transform.translation[session.axis] = new_center;
transform.scale[session.axis] = new_scale;
}
mark_local_transform_dirty(world, session.entity);
}
fn snap_scalar(value: f32, step: f32) -> f32 {
if step <= 0.0 {
value
} else {
(value / step).round() * step
}
}
fn commit_session(editor_world: &mut EditorWorld, world: &mut World) {
let Some(session) = editor_world.resources.push_pull.session.take() else {
return;
};
let Some(new_local) = world.core.get_local_transform(session.entity).copied() else {
return;
};
if new_local.translation != session.original_local.translation
|| new_local.scale != session.original_local.scale
{
editor_world.resources.undo.push(
crate::undo::UndoableOperation::TransformChanged {
uuid: session.uuid,
old: session.original_local,
new: new_local,
},
"Push/pull",
);
crate::scene_writeback::sync_entity(
&mut editor_world.resources.project,
&editor_world.resources.editor_scene,
world,
session.entity,
);
editor_world.resources.ui_handles.inspector.dirty = true;
}
}
fn cancel_session(editor_world: &mut EditorWorld, world: &mut World) {
if let Some(session) = editor_world.resources.push_pull.session.take()
&& let Some(transform) = world.core.get_local_transform_mut(session.entity)
{
*transform = session.original_local;
mark_local_transform_dirty(world, session.entity);
}
clear_highlight(world);
}