use bevy::{input_focus::InputFocus, prelude::*};
use crate::default_style;
use crate::{
selection::Selection, viewport::MainViewportCamera, viewport_util::point_in_polygon_2d,
};
use super::{BrushEditMode, BrushMeshCache, BrushSelection, EditMode};
use jackdaw_geometry::{brush_planes_to_world, compute_brush_geometry};
use jackdaw_jsn::{Brush, BrushFaceData, BrushPlane};
pub(super) fn drop_brush_edit_on_deselect(
input_focus: Res<InputFocus>,
selection: Res<Selection>,
mut edit_mode: ResMut<EditMode>,
mut brush_selection: ResMut<BrushSelection>,
modal: Res<crate::modal_transform::ModalTransformState>,
) {
if input_focus.0.is_some() || modal.active.is_some() {
return;
}
if let EditMode::BrushEdit(_) = *edit_mode
&& let Some(brush_entity) = brush_selection.entity
&& selection.primary() != Some(brush_entity)
{
if !brush_selection.faces.is_empty() {
brush_selection.last_face_entity = Some(brush_entity);
brush_selection.last_face_index = brush_selection.faces.last().copied();
}
*edit_mode = EditMode::Object;
brush_selection.clear();
}
}
pub(crate) struct PendingSubDrag {
pub click_pos: Vec2,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(crate) enum FaceExtrudeMode {
#[default]
Merge, Extend, }
#[derive(Resource, Default)]
pub(crate) struct BrushDragState {
pub pending: Option<PendingSubDrag>,
pub active: bool,
pub extrude_mode: FaceExtrudeMode,
pub quick_action: bool,
pub(crate) start_brush: Option<Brush>,
pub(crate) start_cursor: Vec2,
pub(crate) drag_face_normal: Vec3,
pub extend_face_polygon: Vec<Vec3>,
pub extend_face_normal: Vec3,
pub extend_depth: f32,
pub(crate) drag_camera: Option<Entity>,
pub(crate) drag_viewport: Option<Entity>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(crate) enum VertexDragConstraint {
#[default]
Free,
AxisX,
AxisY,
AxisZ,
}
#[derive(Resource, Default)]
pub(crate) struct VertexDragState {
pub pending: Option<PendingSubDrag>,
pub active: bool,
pub constraint: VertexDragConstraint,
pub(crate) start_brush: Option<Brush>,
pub(crate) start_cursor: Vec2,
pub(crate) start_vertex_positions: Vec<Vec3>,
pub(crate) start_all_vertices: Vec<Vec3>,
pub(crate) start_face_polygons: Vec<Vec<usize>>,
pub(crate) split_vertex: Option<Vec3>,
pub(crate) drag_camera: Option<Entity>,
pub(crate) drag_viewport: Option<Entity>,
}
pub(crate) fn compute_brush_drag_offset(
constraint: VertexDragConstraint,
mouse_delta: Vec2,
cam_tf: &GlobalTransform,
camera: &Camera,
brush_global: &GlobalTransform,
) -> Option<Vec3> {
let brush_pos = brush_global.translation();
let cam_dist = (cam_tf.translation() - brush_pos).length();
let scale = cam_dist * 0.003;
let offset = match constraint {
VertexDragConstraint::Free => {
let cam_right = cam_tf.right().as_vec3();
let cam_up = cam_tf.up().as_vec3();
let world_offset =
cam_right * mouse_delta.x * scale + cam_up * (-mouse_delta.y) * scale;
let (_, brush_rot, _) = brush_global.to_scale_rotation_translation();
brush_rot.inverse() * world_offset
}
constraint => {
let axis_dir = match constraint {
VertexDragConstraint::AxisX => Vec3::X,
VertexDragConstraint::AxisY => Vec3::Y,
VertexDragConstraint::AxisZ => Vec3::Z,
VertexDragConstraint::Free => unreachable!(),
};
let origin_screen = camera.world_to_viewport(cam_tf, brush_pos).ok()?;
let (_, brush_rot, _) = brush_global.to_scale_rotation_translation();
let world_axis = brush_rot * axis_dir;
let axis_screen = camera
.world_to_viewport(cam_tf, brush_pos + world_axis)
.ok()?;
let screen_axis = (axis_screen - origin_screen).normalize_or_zero();
let projected = mouse_delta.dot(screen_axis);
axis_dir * projected * scale
}
};
Some(offset)
}
#[derive(Resource, Default)]
pub(crate) struct EdgeDragState {
pub pending: Option<PendingSubDrag>,
pub active: bool,
pub constraint: VertexDragConstraint,
pub(crate) start_brush: Option<Brush>,
pub(crate) start_cursor: Vec2,
pub(crate) start_edge_vertices: Vec<(usize, Vec3)>,
pub(crate) start_all_vertices: Vec<Vec3>,
pub(crate) start_face_polygons: Vec<Vec<usize>>,
pub(crate) drag_camera: Option<Entity>,
pub(crate) drag_viewport: Option<Entity>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum ClipMode {
#[default]
KeepFront,
KeepBack,
Split,
}
#[derive(Resource, Default)]
pub(crate) struct ClipState {
pub points: Vec<Vec3>,
pub preview_plane: Option<BrushPlane>,
pub mode: ClipMode,
}
pub(super) fn handle_clip_mode(
edit_mode: Res<EditMode>,
camera_query: Query<(Entity, &Camera, &GlobalTransform), With<MainViewportCamera>>,
active: Res<crate::viewport::ActiveViewport>,
brush_selection: Res<BrushSelection>,
brushes: Query<&Brush>,
brush_transforms: Query<&GlobalTransform>,
mut clip_state: ResMut<ClipState>,
mut gizmos: Gizmos,
) {
let EditMode::BrushEdit(BrushEditMode::Clip) = *edit_mode else {
if !clip_state.points.is_empty() || clip_state.mode != ClipMode::KeepFront {
*clip_state = ClipState::default();
}
return;
};
let Some(brush_entity) = brush_selection.entity else {
return;
};
let Ok(brush_global) = brush_transforms.get(brush_entity) else {
return;
};
let cam_tf = active
.camera
.and_then(|e| camera_query.get(e).ok())
.or_else(|| camera_query.iter().next())
.map(|(_, _, tf)| tf);
let Some(cam_tf) = cam_tf else {
return;
};
clip_state.preview_plane = match clip_state.points.len() {
2 => {
let dir = clip_state.points[1] - clip_state.points[0];
let (_, brush_rot, _) = brush_global.to_scale_rotation_translation();
let local_cam_fwd = brush_rot.inverse() * cam_tf.forward().as_vec3();
let normal = dir.cross(local_cam_fwd).normalize_or_zero();
if normal.length_squared() > 0.5 {
let distance = normal.dot(clip_state.points[0]);
Some(BrushPlane { normal, distance })
} else {
None
}
}
3 => {
let a = clip_state.points[0];
let b = clip_state.points[1];
let c = clip_state.points[2];
let normal = (b - a).cross(c - a).normalize_or_zero();
if normal.length_squared() > 0.5 {
let distance = normal.dot(a);
Some(BrushPlane { normal, distance })
} else {
None
}
}
_ => None,
};
let Ok(brush_ref) = brushes.get(brush_entity) else {
return;
};
for (i, point) in clip_state.points.iter().enumerate() {
let world_pos = brush_global.transform_point(*point);
let color = default_style::CLIP_POINT;
gizmos.sphere(Isometry3d::from_translation(world_pos), 0.06, color);
if i > 0 {
let prev_world = brush_global.transform_point(clip_state.points[i - 1]);
gizmos.line(prev_world, world_pos, color);
}
}
if let Some(ref plane) = clip_state.preview_plane {
let (_, brush_rot, brush_trans) = brush_global.to_scale_rotation_translation();
let world_faces = brush_planes_to_world(&brush_ref.faces, brush_rot, brush_trans);
let world_clip_normal = (brush_rot * plane.normal).normalize();
let world_clip_distance = plane.distance + world_clip_normal.dot(brush_trans);
let front_clip = BrushFaceData {
plane: BrushPlane {
normal: world_clip_normal,
distance: world_clip_distance,
},
uv_scale: Vec2::ONE,
..default()
};
let mut front_faces = world_faces.clone();
front_faces.push(front_clip);
let back_clip = BrushFaceData {
plane: BrushPlane {
normal: -world_clip_normal,
distance: -world_clip_distance,
},
uv_scale: Vec2::ONE,
..default()
};
let mut back_faces = world_faces;
back_faces.push(back_clip);
let (front_color, back_color) = match clip_state.mode {
ClipMode::KeepFront => (default_style::CLIP_KEEP, default_style::CLIP_DISCARD),
ClipMode::KeepBack => (default_style::CLIP_DISCARD, default_style::CLIP_KEEP),
ClipMode::Split => (default_style::CLIP_KEEP, default_style::CLIP_SPLIT_BACK),
};
let (verts, polys) = compute_brush_geometry(&front_faces);
if verts.len() >= 4 {
for polygon in &polys {
for i in 0..polygon.len() {
let a = verts[polygon[i]];
let b = verts[polygon[(i + 1) % polygon.len()]];
gizmos.line(a, b, front_color);
}
}
}
let (verts, polys) = compute_brush_geometry(&back_faces);
if verts.len() >= 4 {
for polygon in &polys {
for i in 0..polygon.len() {
let a = verts[polygon[i]];
let b = verts[polygon[(i + 1) % polygon.len()]];
gizmos.line(a, b, back_color);
}
}
}
}
}
fn pick_face_under_cursor(
viewport_cursor: Vec2,
brush_entity: Entity,
camera: &Camera,
cam_tf: &GlobalTransform,
cache: &BrushMeshCache,
face_entities: &Query<(Entity, &super::BrushFaceEntity, &GlobalTransform)>,
) -> Option<usize> {
let mut best_face = None;
let mut best_depth = f32::MAX;
for (_, face_ent, face_global) in face_entities {
if face_ent.brush_entity != brush_entity {
continue;
}
let face_idx = face_ent.face_index;
let polygon = &cache.face_polygons[face_idx];
if polygon.len() < 3 {
continue;
}
let screen_verts: Vec<Vec2> = polygon
.iter()
.filter_map(|&vi| {
let world = face_global.transform_point(cache.vertices[vi]);
camera.world_to_viewport(cam_tf, world).ok()
})
.collect();
if screen_verts.len() < 3 {
continue;
}
if point_in_polygon_2d(viewport_cursor, &screen_verts) {
let centroid: Vec3 =
polygon.iter().map(|&vi| cache.vertices[vi]).sum::<Vec3>() / polygon.len() as f32;
let world_centroid = face_global.transform_point(centroid);
let depth = (cam_tf.translation() - world_centroid).length_squared();
if depth < best_depth {
best_depth = depth;
best_face = Some(face_idx);
}
}
}
best_face
}
pub(super) fn brush_face_hover(
edit_mode: Res<EditMode>,
keyboard: Res<ButtonInput<KeyCode>>,
vp: crate::viewport::ViewportCursor,
face_entities: Query<(Entity, &super::BrushFaceEntity, &GlobalTransform)>,
brush_selection: Res<BrushSelection>,
brush_caches: Query<&BrushMeshCache>,
selection: Res<Selection>,
drag_state: Res<BrushDragState>,
mut hover: ResMut<super::BrushFaceHover>,
brushes: Query<(), With<Brush>>,
) {
let in_face_edit = matches!(*edit_mode, EditMode::BrushEdit(BrushEditMode::Face));
let shift = keyboard.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]);
let alt = keyboard.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]);
if drag_state.active {
hover.entity = None;
hover.face_index = None;
return;
}
let should_hover = in_face_edit || (*edit_mode == EditMode::Object && (shift || alt));
if !should_hover {
hover.entity = None;
hover.face_index = None;
return;
}
let intent = if alt {
super::HoverIntent::Extend
} else {
super::HoverIntent::PushPull
};
let Ok(window) = vp.windows.single() else {
hover.entity = None;
hover.face_index = None;
return;
};
let Some(cursor_pos) = window.cursor_position() else {
hover.entity = None;
hover.face_index = None;
return;
};
let Some(camera_entity) = vp.camera_entity() else {
hover.entity = None;
hover.face_index = None;
return;
};
let Some(viewport_entity) = vp.viewport_entity() else {
hover.entity = None;
hover.face_index = None;
return;
};
let Some((camera, cam_tf)) = vp.camera_for(camera_entity) else {
hover.entity = None;
hover.face_index = None;
return;
};
let Some(viewport_cursor) = vp.viewport_cursor_for(camera, viewport_entity, cursor_pos) else {
hover.entity = None;
hover.face_index = None;
return;
};
let brush_entity = if in_face_edit {
brush_selection.entity
} else {
selection.primary().filter(|&e| brushes.contains(e))
};
let Some(brush_entity) = brush_entity else {
hover.entity = None;
hover.face_index = None;
return;
};
let Ok(cache) = brush_caches.get(brush_entity) else {
hover.entity = None;
hover.face_index = None;
return;
};
if let Some(face_idx) = pick_face_under_cursor(
viewport_cursor,
brush_entity,
camera,
cam_tf,
cache,
&face_entities,
) {
hover.entity = Some(brush_entity);
hover.face_index = Some(face_idx);
hover.intent = intent;
} else {
hover.entity = None;
hover.face_index = None;
}
}