mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
impl detection
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -53,5 +53,5 @@
|
||||
"files.associations": {
|
||||
"*.graphite": "json"
|
||||
},
|
||||
"rust-analyzer.checkOnSave": false
|
||||
"rust-analyzer.checkOnSave": true
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints};
|
||||
use kurbo::{Line, ParamCurveNearest, Rect};
|
||||
use kurbo::{Line, ParamCurveNearest, Rect, Shape, Triangle};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
@@ -63,13 +63,15 @@ impl GridSpacingGizmo {
|
||||
};
|
||||
let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface);
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
if let Some((col, row)) = check_if_over_gizmo(grid_type, columns, rows, spacing, mouse_position, viewport) {
|
||||
if let Some((col, row)) = check_if_over_gizmo(grid_type, columns, rows, spacing, angles, mouse_position, viewport) {
|
||||
log::info!("col {:?} , row {:?}", col, row);
|
||||
self.layer = Some(layer);
|
||||
self.column_index = col;
|
||||
self.row_index = row;
|
||||
self.initial_spacing = spacing;
|
||||
self.update_state(GridSpacingGizmoState::Hover);
|
||||
let closest_gizmo = GridSpacingGizmoType::get_closest_line(mouse_position, col, row, spacing, viewport, stroke_width);
|
||||
let closest_gizmo = GridSpacingGizmoType::get_closest_line(grid_type, mouse_position, col, row, spacing, angles, viewport, stroke_width);
|
||||
log::info!("gizmo type {:?}", closest_gizmo);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: closest_gizmo.mouse_icon() });
|
||||
self.gizmo_type = Some(closest_gizmo);
|
||||
}
|
||||
@@ -85,13 +87,22 @@ impl GridSpacingGizmo {
|
||||
|
||||
match self.gizmo_state {
|
||||
GridSpacingGizmoState::Inactive => {}
|
||||
GridSpacingGizmoState::Hover | GridSpacingGizmoState::Dragging => {
|
||||
if let Some(gizmo_type) = &self.gizmo_type {
|
||||
let line = gizmo_type.line(self.column_index, self.row_index, spacing, viewport, stroke_width);
|
||||
let (p0, p1) = get_line_endpoints(line);
|
||||
overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5));
|
||||
GridSpacingGizmoState::Hover | GridSpacingGizmoState::Dragging => match grid_type {
|
||||
GridType::Rectangular => {
|
||||
if let Some(gizmo_type) = &self.gizmo_type {
|
||||
let line = gizmo_type.line(grid_type, self.column_index, self.row_index, angles, spacing, viewport, stroke_width);
|
||||
let (p0, p1) = get_line_endpoints(line);
|
||||
overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
GridType::Isometric => {
|
||||
if let Some(gizmo_type) = &self.gizmo_type {
|
||||
let line = gizmo_type.line(grid_type, self.column_index, self.row_index, angles, spacing, viewport, stroke_width);
|
||||
let (p0, p1) = get_line_endpoints(line);
|
||||
overlay_context.dashed_line(p0, p1, None, None, Some(5.), Some(5.), Some(0.5));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +114,9 @@ impl GridSpacingGizmo {
|
||||
return;
|
||||
};
|
||||
|
||||
let stroke_width = graph_modification_utils::get_stroke_width(layer, &document.network_interface);
|
||||
let Some(gizmo_type) = &self.gizmo_type else { return };
|
||||
let direction = gizmo_type.direction(self.initial_spacing, viewport);
|
||||
let direction = gizmo_type.direction(self.column_index, self.row_index, angles, self.initial_spacing, viewport);
|
||||
let delta_vector = input.mouse.position - drag_start;
|
||||
|
||||
let delta = delta_vector.dot(direction);
|
||||
@@ -121,7 +133,7 @@ impl GridSpacingGizmo {
|
||||
input: NodeInput::value(TaggedValue::DVec2(new_spacing), false),
|
||||
});
|
||||
|
||||
let transform = self.transform_grid(spacing_delta, direction);
|
||||
let transform = self.gizmo_type.as_ref().unwrap().transform_grid(spacing_delta, direction, self.column_index, self.row_index);
|
||||
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
layer,
|
||||
@@ -140,159 +152,159 @@ impl GridSpacingGizmo {
|
||||
// self.initial_columns = 1;
|
||||
// }
|
||||
}
|
||||
|
||||
fn transform_grid(&self, spacing_delta: DVec2, direction: DVec2) -> DAffine2 {
|
||||
if let Some(gizmo_type) = &self.gizmo_type {
|
||||
match gizmo_type {
|
||||
GridSpacingGizmoType::Right => {
|
||||
if self.column_index == 0 {
|
||||
DAffine2::IDENTITY
|
||||
} else {
|
||||
DAffine2::from_translation(-spacing_delta * direction * (self.column_index) as f64)
|
||||
}
|
||||
}
|
||||
GridSpacingGizmoType::Down => {
|
||||
if self.row_index == 0 {
|
||||
DAffine2::IDENTITY
|
||||
} else {
|
||||
DAffine2::from_translation(-spacing_delta * direction * (self.row_index) as f64)
|
||||
}
|
||||
}
|
||||
GridSpacingGizmoType::Left => {
|
||||
if self.column_index == 0 {
|
||||
DAffine2::from_translation(spacing_delta * direction)
|
||||
} else {
|
||||
DAffine2::from_translation(spacing_delta * direction * (self.column_index + 1) as f64)
|
||||
}
|
||||
}
|
||||
GridSpacingGizmoType::Top => {
|
||||
if self.row_index == 0 {
|
||||
DAffine2::from_translation(spacing_delta * direction)
|
||||
} else {
|
||||
DAffine2::from_translation(spacing_delta * direction * (self.row_index + 1) as f64)
|
||||
}
|
||||
}
|
||||
|
||||
GridSpacingGizmoType::None => DAffine2::IDENTITY,
|
||||
_ => DAffine2::from_translation(spacing_delta * direction),
|
||||
}
|
||||
} else {
|
||||
DAffine2::IDENTITY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<(u32, u32)> {
|
||||
fn check_if_over_gizmo(grid_type: GridType, columns: u32, rows: u32, spacing: DVec2, angles: DVec2, mouse_position: DVec2, viewport: DAffine2) -> Option<(u32, u32)> {
|
||||
let layer_mouse = viewport.inverse().transform_point2(mouse_position);
|
||||
for column in 0..columns - 1 {
|
||||
for row in 0..rows - 1 {
|
||||
let p0 = DVec2::new(spacing.x * column as f64, spacing.y * row as f64);
|
||||
let p1 = DVec2::new((1 + column) as f64 * spacing.x, (1 + row) as f64 * spacing.y);
|
||||
let rect = Rect::from_points(dvec2_to_point(p0), dvec2_to_point(p1));
|
||||
match grid_type {
|
||||
GridType::Rectangular => {
|
||||
for column in 0..columns - 1 {
|
||||
for row in 0..rows - 1 {
|
||||
let p0 = DVec2::new(spacing.x * column as f64, spacing.y * row as f64);
|
||||
let p1 = DVec2::new((1 + column) as f64 * spacing.x, (1 + row) as f64 * spacing.y);
|
||||
let rect = Rect::from_points(dvec2_to_point(p0), dvec2_to_point(p1));
|
||||
|
||||
if rect.contains(dvec2_to_point(layer_mouse)) {
|
||||
return Some((column, row));
|
||||
};
|
||||
if rect.contains(dvec2_to_point(layer_mouse)) {
|
||||
return Some((column, row));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
GridType::Isometric => {
|
||||
for column in 0..columns - 1 {
|
||||
for row in 0..rows - 1 {
|
||||
let p0 = isometric_point_position(row, column, spacing, angles);
|
||||
let p1 = isometric_point_position(row, column + 1, spacing, angles);
|
||||
let p2 = isometric_point_position(row + 1, column + 1, spacing, angles);
|
||||
let p4 = isometric_point_position(row + 1, column, spacing, angles);
|
||||
|
||||
let triangle1 = Triangle::new(dvec2_to_point(p0), dvec2_to_point(p1), dvec2_to_point(p2));
|
||||
let triangle2 = Triangle::new(dvec2_to_point(p0), dvec2_to_point(p2), dvec2_to_point(p4));
|
||||
|
||||
if triangle2.contains(dvec2_to_point(layer_mouse)) {
|
||||
return Some((column, row));
|
||||
}
|
||||
|
||||
if triangle1.contains(dvec2_to_point(layer_mouse)) {
|
||||
return Some((column, row));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum GridSpacingGizmoType {
|
||||
#[default]
|
||||
None,
|
||||
Top,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
// #[derive(Clone, Debug, Default, PartialEq)]
|
||||
// pub enum GridSpacingGizmoType {
|
||||
// #[default]
|
||||
// None,
|
||||
// Top,
|
||||
// Down,
|
||||
// Left,
|
||||
// Right,
|
||||
// IsometricMiddleUp,
|
||||
// IsometricMiddleDown,
|
||||
// }
|
||||
|
||||
impl GridSpacingGizmoType {
|
||||
pub fn get_line_points(&self, column_index: u32, row_index: u32, spacing: DVec2, stroke: Option<f64>) -> (DVec2, DVec2) {
|
||||
match self {
|
||||
Self::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke),
|
||||
Self::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke),
|
||||
Self::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke),
|
||||
Self::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke),
|
||||
Self::None => panic!("RowColumnGizmoType::None does not have line points"),
|
||||
}
|
||||
}
|
||||
// impl GridSpacingGizmoType {
|
||||
// pub fn get_line_points(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke: Option<f64>) -> (DVec2, DVec2) {
|
||||
// match grid_type {
|
||||
// GridType::Rectangular => match self {
|
||||
// Self::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke),
|
||||
// Self::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke),
|
||||
// Self::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke),
|
||||
// Self::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke),
|
||||
// Self::None => panic!("RowColumnGizmoType::None does not have line points"),
|
||||
// },
|
||||
// GridType::Isometric => match self {
|
||||
// Self::Top => get_isometric_top_points(column_index, row_index, angles, spacing, stroke),
|
||||
// Self::Right => get_isometric_right_points(column_index, row_index, angles, spacing, stroke),
|
||||
// Self::Down => get_isometric_down_points(column_index, row_index, angles, spacing, stroke),
|
||||
// Self::Left => get_isometric_left_points(column_index, row_index, angles, spacing, stroke),
|
||||
// Self::None => panic!("RowColumnGizmoType::None does not have line points"),
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn get_closest_line(mouse_position: DVec2, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> Self {
|
||||
let mut gizmo_type = GridSpacingGizmoType::Top;
|
||||
let mut closest_distance = gizmo_type
|
||||
.line(column_index, row_index, spacing, viewport, stroke_width)
|
||||
.nearest(dvec2_to_point(mouse_position), 1e-6)
|
||||
.distance_sq;
|
||||
// pub fn get_closest_line(mouse_position: DVec2, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> Self {
|
||||
// let mut gizmo_type = GridSpacingGizmoType::Top;
|
||||
// let mut closest_distance = gizmo_type
|
||||
// .line(grid_type, column_index, row_index, angles, spacing, viewport, stroke_width)
|
||||
// .nearest(dvec2_to_point(mouse_position), 1e-6)
|
||||
// .distance_sq;
|
||||
|
||||
for t in Self::all() {
|
||||
if matches!(t, GridSpacingGizmoType::Top) {
|
||||
continue;
|
||||
}
|
||||
let line = t.line(column_index, row_index, spacing, viewport, stroke_width);
|
||||
let nearest = line.nearest(dvec2_to_point(mouse_position), 1e-6);
|
||||
if nearest.distance_sq < closest_distance {
|
||||
gizmo_type = t;
|
||||
closest_distance = nearest.distance_sq;
|
||||
}
|
||||
}
|
||||
gizmo_type
|
||||
}
|
||||
// for t in Self::all() {
|
||||
// if matches!(t, GridSpacingGizmoType::Top) {
|
||||
// continue;
|
||||
// }
|
||||
// let line = t.line(grid_type, column_index, row_index, angles, spacing, viewport, stroke_width);
|
||||
// let nearest = line.nearest(dvec2_to_point(mouse_position), 1e-6);
|
||||
// if nearest.distance_sq < closest_distance {
|
||||
// gizmo_type = t;
|
||||
// closest_distance = nearest.distance_sq;
|
||||
// }
|
||||
// }
|
||||
// gizmo_type
|
||||
// }
|
||||
|
||||
pub fn line(&self, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> Line {
|
||||
let (p0, p1) = self.get_line_points(column_index, row_index, spacing, stroke_width);
|
||||
let gap = -5. * self.direction(spacing, viewport);
|
||||
// pub fn line(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> Line {
|
||||
// let (p0, p1) = self.get_line_points(grid_type, column_index, row_index, angles, spacing, stroke_width);
|
||||
// // let gap = 2. * stroke_width.unwrap_or(3.) * (p1 - p0).perp().normalize();
|
||||
|
||||
convert_to_gizmo_line(viewport.transform_point2(p0) + gap, viewport.transform_point2(p1) + gap)
|
||||
}
|
||||
// convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1))
|
||||
// }
|
||||
|
||||
fn opposite(&self, grid_type: GridType, column_index: u32, row_index: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2, stroke: Option<f64>) -> Line {
|
||||
let opposite_gizmo_type = self.opposite_gizmo_type();
|
||||
opposite_gizmo_type.line(column_index, row_index, spacing, viewport, stroke)
|
||||
}
|
||||
// fn opposite_gizmo_type(&self) -> Self {
|
||||
// return match self {
|
||||
// Self::Top => Self::Down,
|
||||
// Self::Right => Self::Left,
|
||||
// Self::Down => Self::Top,
|
||||
// Self::Left => Self::Right,
|
||||
// Self::None => panic!("RowColumnGizmoType::None does not have opposite"),
|
||||
// };
|
||||
// }
|
||||
|
||||
fn opposite_gizmo_type(&self) -> Self {
|
||||
return match self {
|
||||
Self::Top => Self::Down,
|
||||
Self::Right => Self::Left,
|
||||
Self::Down => Self::Top,
|
||||
Self::Left => Self::Right,
|
||||
Self::None => panic!("RowColumnGizmoType::None does not have opposite"),
|
||||
};
|
||||
}
|
||||
// fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 {
|
||||
// match self {
|
||||
// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta),
|
||||
// GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y),
|
||||
// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||
// }
|
||||
// }
|
||||
|
||||
fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 {
|
||||
match self {
|
||||
GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta),
|
||||
GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y),
|
||||
GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||
}
|
||||
}
|
||||
// fn direction(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 {
|
||||
// match grid_type {
|
||||
// GridType::Rectangular => match self {
|
||||
// GridSpacingGizmoType::Top => viewport.transform_vector2(DVec2::Y),
|
||||
// GridSpacingGizmoType::Down => -viewport.transform_vector2(DVec2::Y),
|
||||
// GridSpacingGizmoType::Right => calculate_rectangle_side_direction(spacing, viewport),
|
||||
// GridSpacingGizmoType::Left => -calculate_rectangle_side_direction(spacing, viewport),
|
||||
// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"),
|
||||
// },
|
||||
// GridType::Isometric => match &self {
|
||||
// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down | GridSpacingGizmoType::Left | GridSpacingGizmoType::Right => {
|
||||
// let (p1, p2) = self.get_line_points(grid_type, column_index, row_index, angles, spacing, None);
|
||||
// (p1 - p2).perp().normalize()
|
||||
// }
|
||||
// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"),
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
|
||||
fn direction(&self, spacing: DVec2, viewport: DAffine2) -> DVec2 {
|
||||
match self {
|
||||
GridSpacingGizmoType::Top => calculate_rectangle_top_direction(spacing, viewport),
|
||||
GridSpacingGizmoType::Down => -calculate_rectangle_top_direction(spacing, viewport),
|
||||
GridSpacingGizmoType::Right => calculate_rectangle_side_direction(spacing, viewport),
|
||||
GridSpacingGizmoType::Left => -calculate_rectangle_side_direction(spacing, viewport),
|
||||
GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a line"),
|
||||
}
|
||||
}
|
||||
// fn mouse_icon(&self) -> MouseCursorIcon {
|
||||
// match self {
|
||||
// GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => MouseCursorIcon::NSResize,
|
||||
// GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => MouseCursorIcon::EWResize,
|
||||
// GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||
// }
|
||||
// }
|
||||
|
||||
fn mouse_icon(&self) -> MouseCursorIcon {
|
||||
match self {
|
||||
GridSpacingGizmoType::Top | GridSpacingGizmoType::Down => MouseCursorIcon::NSResize,
|
||||
GridSpacingGizmoType::Right | GridSpacingGizmoType::Left => MouseCursorIcon::EWResize,
|
||||
GridSpacingGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all() -> [Self; 4] {
|
||||
[Self::Top, Self::Right, Self::Down, Self::Left]
|
||||
}
|
||||
}
|
||||
// pub fn all() -> [Self; 4] {
|
||||
// [Self::Top, Self::Right, Self::Down, Self::Left]
|
||||
// }
|
||||
// }
|
||||
|
||||
fn get_rectangular_top_points(column_index: u32, row_index: u32, spacing: DVec2, stroke_width: Option<f64>) -> (DVec2, DVec2) {
|
||||
let stroke_width = stroke_width.unwrap_or_default();
|
||||
@@ -327,3 +339,376 @@ fn get_rectangular_left_points(column_index: u32, row_index: u32, spacing: DVec2
|
||||
|
||||
(p0, p1)
|
||||
}
|
||||
|
||||
fn isometric_point_position(row: u32, col: u32, spacing: DVec2, angles: DVec2) -> DVec2 {
|
||||
let (angle_a, angle_b) = angles.into();
|
||||
let tan_a = angle_a.to_radians().tan();
|
||||
let tan_b = angle_b.to_radians().tan();
|
||||
|
||||
let spacing = DVec2::new(spacing.y / (tan_a + tan_b), spacing.y);
|
||||
|
||||
let a_angles_eaten = col.div_ceil(2) as f64;
|
||||
let b_angles_eaten = (col / 2) as f64;
|
||||
let offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a;
|
||||
|
||||
DVec2::new(spacing.x * col as f64, spacing.y * row as f64 + offset_y_fraction * spacing.x)
|
||||
}
|
||||
|
||||
fn get_isometric_top_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option<f64>) -> (DVec2, DVec2) {
|
||||
let stroke_width = stroke_width.unwrap_or_default();
|
||||
|
||||
let x0 = isometric_point_position(row_index, column_index, spacing, angles);
|
||||
let x1 = isometric_point_position(row_index, column_index + 1, spacing, angles);
|
||||
let direction = (x1 - x0).normalize();
|
||||
|
||||
// in the direction of edge
|
||||
let padding = (x1 - x0).length() * 0.1 * direction;
|
||||
// perpendicular to the edge
|
||||
let push_out = calculate_gap_vector(direction, stroke_width);
|
||||
|
||||
(x0 + push_out + padding, x1 + push_out - padding)
|
||||
}
|
||||
|
||||
fn get_isometric_right_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option<f64>) -> (DVec2, DVec2) {
|
||||
let stroke_width = stroke_width.unwrap_or_default();
|
||||
|
||||
let x0 = isometric_point_position(row_index, column_index + 1, spacing, angles);
|
||||
let x1 = isometric_point_position(row_index + 1, column_index + 1, spacing, angles);
|
||||
let direction = (x1 - x0).normalize();
|
||||
|
||||
// in the direction of edge
|
||||
let padding = (x1 - x0).length() * 0.1 * direction;
|
||||
// perpendicular to the edge
|
||||
let push_out = calculate_gap_vector(direction, stroke_width);
|
||||
|
||||
(x0 + push_out + padding, x1 + push_out - padding)
|
||||
}
|
||||
|
||||
fn get_isometric_down_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option<f64>) -> (DVec2, DVec2) {
|
||||
let stroke_width = stroke_width.unwrap_or_default();
|
||||
|
||||
let x0 = isometric_point_position(row_index + 1, column_index, spacing, angles);
|
||||
let x1 = isometric_point_position(row_index + 1, column_index + 1, spacing, angles);
|
||||
let direction = (x1 - x0).normalize();
|
||||
|
||||
// in the direction of edge
|
||||
let padding = (x1 - x0).length() * 0.1 * direction;
|
||||
// perpendicular to the edge
|
||||
let push_out = calculate_gap_vector(direction, stroke_width);
|
||||
|
||||
(x0 - push_out + padding, x1 - push_out - padding)
|
||||
}
|
||||
|
||||
fn get_isometric_left_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option<f64>) -> (DVec2, DVec2) {
|
||||
let stroke_width = stroke_width.unwrap_or_default();
|
||||
|
||||
let x0 = isometric_point_position(row_index, column_index, spacing, angles);
|
||||
let x1 = isometric_point_position(row_index + 1, column_index, spacing, angles);
|
||||
let direction = (x1 - x0).normalize();
|
||||
|
||||
// in the direction of edge
|
||||
let padding = (x1 - x0).length() * 0.1 * direction;
|
||||
// perpendicular to the edge
|
||||
let push_out = calculate_gap_vector(direction, stroke_width);
|
||||
|
||||
(x0 - push_out + padding, x1 - push_out - padding)
|
||||
}
|
||||
|
||||
fn get_isometric_middle_up_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option<f64>) -> (DVec2, DVec2) {
|
||||
let stroke_width = stroke_width.unwrap_or_default();
|
||||
|
||||
let (x0, x1) = if column_index % 2 == 0 {
|
||||
(
|
||||
isometric_point_position(row_index, column_index, spacing, angles),
|
||||
isometric_point_position(row_index + 1, column_index + 1, spacing, angles),
|
||||
)
|
||||
} else {
|
||||
// ref point is changed
|
||||
(
|
||||
isometric_point_position(row_index + 1, column_index, spacing, angles),
|
||||
isometric_point_position(row_index, column_index + 1, spacing, angles),
|
||||
)
|
||||
};
|
||||
let direction = (x1 - x0).normalize();
|
||||
|
||||
// in the direction of edge
|
||||
let padding = (x1 - x0).length() * 0.1 * direction;
|
||||
// perpendicular to the edge
|
||||
let push_out = calculate_gap_vector(direction, stroke_width);
|
||||
|
||||
(x0 - push_out + padding, x1 - push_out - padding)
|
||||
}
|
||||
|
||||
fn get_isometric_middle_down_points(column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke_width: Option<f64>) -> (DVec2, DVec2) {
|
||||
let stroke_width = stroke_width.unwrap_or_default();
|
||||
|
||||
let (x0, x1) = if column_index % 2 == 0 {
|
||||
(
|
||||
isometric_point_position(row_index, column_index, spacing, angles),
|
||||
isometric_point_position(row_index + 1, column_index + 1, spacing, angles),
|
||||
)
|
||||
} else {
|
||||
// ref point is changed
|
||||
(
|
||||
isometric_point_position(row_index + 1, column_index, spacing, angles),
|
||||
isometric_point_position(row_index, column_index + 1, spacing, angles),
|
||||
)
|
||||
};
|
||||
let direction = (x1 - x0).normalize();
|
||||
// in the direction of edge
|
||||
let padding = (x1 - x0).length() * 0.1 * direction;
|
||||
// perpendicular to the edge
|
||||
let push_out = calculate_gap_vector(direction, stroke_width);
|
||||
|
||||
(x0 + push_out + padding, x1 + push_out - padding)
|
||||
}
|
||||
|
||||
fn calculate_gap_vector(direction: DVec2, stroke_width: f64) -> DVec2 {
|
||||
let perp = direction.perp().normalize();
|
||||
(stroke_width + 0.5) * perp
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub enum RectangularGizmoType {
|
||||
#[default]
|
||||
Top,
|
||||
Right,
|
||||
Down,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub enum IsometricGizmoType {
|
||||
#[default]
|
||||
Top,
|
||||
Right,
|
||||
Down,
|
||||
Left,
|
||||
IsometricMiddleUp,
|
||||
IsometricMiddleDown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GridSpacingGizmoType {
|
||||
Rect(RectangularGizmoType),
|
||||
Iso(IsometricGizmoType),
|
||||
}
|
||||
|
||||
pub fn get_line_points_for_rect(gizmo: RectangularGizmoType, column_index: u32, row_index: u32, spacing: DVec2, stroke: Option<f64>) -> (DVec2, DVec2) {
|
||||
match gizmo {
|
||||
RectangularGizmoType::Top => get_rectangular_top_points(column_index, row_index, spacing, stroke),
|
||||
RectangularGizmoType::Right => get_rectangular_right_points(column_index, row_index, spacing, stroke),
|
||||
RectangularGizmoType::Down => get_rectangular_down_points(column_index, row_index, spacing, stroke),
|
||||
RectangularGizmoType::Left => get_rectangular_left_points(column_index, row_index, spacing, stroke),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_line_points_for_iso(gizmo: IsometricGizmoType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, stroke: Option<f64>) -> (DVec2, DVec2) {
|
||||
match gizmo {
|
||||
IsometricGizmoType::Top => get_isometric_top_points(column_index, row_index, angles, spacing, stroke),
|
||||
IsometricGizmoType::Right => get_isometric_right_points(column_index, row_index, angles, spacing, stroke),
|
||||
IsometricGizmoType::Down => get_isometric_down_points(column_index, row_index, angles, spacing, stroke),
|
||||
IsometricGizmoType::Left => get_isometric_left_points(column_index, row_index, angles, spacing, stroke),
|
||||
IsometricGizmoType::IsometricMiddleUp => get_isometric_middle_up_points(column_index, row_index, angles, spacing, stroke),
|
||||
IsometricGizmoType::IsometricMiddleDown => get_isometric_middle_down_points(column_index, row_index, angles, spacing, stroke),
|
||||
}
|
||||
}
|
||||
|
||||
// Builds a Line after viewport transform
|
||||
pub fn gizmo_line_from_points(p0: DVec2, p1: DVec2, viewport: DAffine2) -> Line {
|
||||
convert_to_gizmo_line(viewport.transform_point2(p0), viewport.transform_point2(p1))
|
||||
}
|
||||
|
||||
// pub fn gizmo_opposite_rect(g: RectangularGizmoType) -> RectangularGizmoType {
|
||||
// match g {
|
||||
// RectangularGizmoType::Top => RectangularGizmoType::Down,
|
||||
// RectangularGizmoType::Down => RectangularGizmoType::Top,
|
||||
// RectangularGizmoType::Right => RectangularGizmoType::Left,
|
||||
// RectangularGizmoType::Left => RectangularGizmoType::Right,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn gizmo_opposite_iso(g: IsometricGizmoType) -> IsometricGizmoType {
|
||||
// match g {
|
||||
// IsometricGizmoType::Top => IsometricGizmoType::Down,
|
||||
// IsometricGizmoType::Down => IsometricGizmoType::Top,
|
||||
// IsometricGizmoType::Right => IsometricGizmoType::Left,
|
||||
// IsometricGizmoType::Left => IsometricGizmoType::Right,
|
||||
// IsometricGizmoType::IsometricMiddleUp => IsometricGizmoType::IsometricMiddleDown,
|
||||
// IsometricGizmoType::IsometricMiddleDown => IsometricGizmoType::IsometricMiddleUp,
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn gizmo_new_spacing_rect(g: RectangularGizmoType, delta: f64, spacing: DVec2) -> DVec2 {
|
||||
match g {
|
||||
RectangularGizmoType::Top | RectangularGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta),
|
||||
RectangularGizmoType::Right | RectangularGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gizmo_new_spacing_iso(g: IsometricGizmoType, delta: f64, spacing: DVec2) -> DVec2 {
|
||||
match g {
|
||||
IsometricGizmoType::Top | IsometricGizmoType::Down => DVec2::new(spacing.x, spacing.y + delta),
|
||||
IsometricGizmoType::Right | IsometricGizmoType::Left => DVec2::new(spacing.x + delta, spacing.y),
|
||||
IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => {
|
||||
// Adjust both? depends on your desired behavior
|
||||
DVec2::new(spacing.x + delta, spacing.y + delta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gizmo_direction_rect(g: RectangularGizmoType, spacing: DVec2, viewport: DAffine2) -> DVec2 {
|
||||
match g {
|
||||
RectangularGizmoType::Top => viewport.transform_vector2(DVec2::Y),
|
||||
RectangularGizmoType::Down => -viewport.transform_vector2(DVec2::Y),
|
||||
RectangularGizmoType::Right => viewport.transform_vector2(DVec2::X),
|
||||
RectangularGizmoType::Left => -viewport.transform_vector2(-DVec2::X),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gizmo_direction_iso(g: IsometricGizmoType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2) -> DVec2 {
|
||||
let (p1, p2) = get_line_points_for_iso(g, column_index, row_index, angles, spacing, None);
|
||||
(p1 - p2).perp().normalize()
|
||||
}
|
||||
|
||||
pub fn gizmo_mouse_icon_rect(g: RectangularGizmoType) -> MouseCursorIcon {
|
||||
match g {
|
||||
RectangularGizmoType::Top | RectangularGizmoType::Down => MouseCursorIcon::NSResize,
|
||||
RectangularGizmoType::Right | RectangularGizmoType::Left => MouseCursorIcon::EWResize,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gizmo_mouse_icon_iso(g: IsometricGizmoType) -> MouseCursorIcon {
|
||||
match g {
|
||||
IsometricGizmoType::Top | IsometricGizmoType::Down | IsometricGizmoType::IsometricMiddleUp | IsometricGizmoType::IsometricMiddleDown => MouseCursorIcon::NSResize,
|
||||
IsometricGizmoType::Right | IsometricGizmoType::Left => MouseCursorIcon::EWResize,
|
||||
}
|
||||
}
|
||||
|
||||
impl RectangularGizmoType {
|
||||
pub fn all() -> [Self; 4] {
|
||||
[Self::Top, Self::Right, Self::Down, Self::Left]
|
||||
}
|
||||
}
|
||||
|
||||
impl IsometricGizmoType {
|
||||
pub fn all() -> [Self; 6] {
|
||||
[Self::Top, Self::Right, Self::Down, Self::Left, Self::IsometricMiddleUp, Self::IsometricMiddleDown]
|
||||
}
|
||||
}
|
||||
|
||||
impl GridSpacingGizmoType {
|
||||
pub fn line(&self, grid_type: GridType, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> Line {
|
||||
match self {
|
||||
GridSpacingGizmoType::Rect(g) => {
|
||||
let (p0, p1) = get_line_points_for_rect(*g, column_index, row_index, spacing, stroke_width);
|
||||
gizmo_line_from_points(p0, p1, viewport)
|
||||
}
|
||||
GridSpacingGizmoType::Iso(g) => {
|
||||
let (p0, p1) = get_line_points_for_iso(*g, column_index, row_index, angles, spacing, stroke_width);
|
||||
gizmo_line_from_points(p0, p1, viewport)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_closest_line(grid_type: GridType, mouse_position: DVec2, column_index: u32, row_index: u32, spacing: DVec2, angles: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> Self {
|
||||
match grid_type {
|
||||
GridType::Rectangular => Self::Rect(closest_line_rect(mouse_position, column_index, row_index, spacing, viewport, stroke_width)),
|
||||
GridType::Isometric => Self::Iso(closest_line_iso(mouse_position, column_index, row_index, angles, spacing, viewport, stroke_width)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn direction(&self, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2) -> DVec2 {
|
||||
match &self {
|
||||
GridSpacingGizmoType::Rect(g) => gizmo_direction_rect(*g, spacing, viewport),
|
||||
GridSpacingGizmoType::Iso(g) => gizmo_direction_iso(*g, column_index, row_index, angles, spacing),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_spacing(&self, delta: f64, spacing: DVec2) -> DVec2 {
|
||||
match &self {
|
||||
GridSpacingGizmoType::Rect(g) => gizmo_new_spacing_rect(*g, delta, spacing),
|
||||
GridSpacingGizmoType::Iso(g) => gizmo_new_spacing_iso(*g, delta, spacing),
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_icon(&self) -> MouseCursorIcon {
|
||||
match self {
|
||||
GridSpacingGizmoType::Rect(g) => gizmo_mouse_icon_rect(*g),
|
||||
GridSpacingGizmoType::Iso(g) => gizmo_mouse_icon_iso(*g),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_grid(&self, spacing_delta: DVec2, direction: DVec2, column_index: u32, row_index: u32) -> DAffine2 {
|
||||
match self {
|
||||
GridSpacingGizmoType::Rect(gizmo_type) => match gizmo_type {
|
||||
RectangularGizmoType::Right => {
|
||||
if column_index == 0 {
|
||||
DAffine2::IDENTITY
|
||||
} else {
|
||||
DAffine2::from_translation(-spacing_delta * direction * column_index as f64)
|
||||
}
|
||||
}
|
||||
RectangularGizmoType::Down => {
|
||||
if row_index == 0 {
|
||||
DAffine2::IDENTITY
|
||||
} else {
|
||||
DAffine2::from_translation(-spacing_delta * direction * row_index as f64)
|
||||
}
|
||||
}
|
||||
RectangularGizmoType::Left => {
|
||||
if column_index == 0 {
|
||||
DAffine2::from_translation(spacing_delta * direction)
|
||||
} else {
|
||||
DAffine2::from_translation(spacing_delta * direction * (column_index + 1) as f64)
|
||||
}
|
||||
}
|
||||
RectangularGizmoType::Top => {
|
||||
if row_index == 0 {
|
||||
DAffine2::from_translation(spacing_delta * direction)
|
||||
} else {
|
||||
DAffine2::from_translation(spacing_delta * direction * (row_index + 1) as f64)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
GridSpacingGizmoType::Iso(_) => {
|
||||
// Placeholder: no transformation for now
|
||||
DAffine2::IDENTITY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn closest_line_generic<T>(mouse_position: DVec2, viewport: DAffine2, all_variants: &[T], get_line_points: impl Fn(T) -> (DVec2, DVec2)) -> T
|
||||
where
|
||||
T: Copy + PartialEq,
|
||||
{
|
||||
let mut gizmo_type = all_variants[0];
|
||||
let mut closest_distance = {
|
||||
let (p0, p1) = get_line_points(gizmo_type);
|
||||
gizmo_line_from_points(p0, p1, viewport).nearest(dvec2_to_point(mouse_position), 1e-6).distance_sq
|
||||
};
|
||||
|
||||
for &t in all_variants.iter().skip(1) {
|
||||
let (p0, p1) = get_line_points(t);
|
||||
let nearest = gizmo_line_from_points(p0, p1, viewport).nearest(dvec2_to_point(mouse_position), 1e-6);
|
||||
if nearest.distance_sq < closest_distance {
|
||||
gizmo_type = t;
|
||||
closest_distance = nearest.distance_sq;
|
||||
}
|
||||
}
|
||||
gizmo_type
|
||||
}
|
||||
|
||||
pub fn closest_line_rect(mouse_position: DVec2, column_index: u32, row_index: u32, spacing: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> RectangularGizmoType {
|
||||
closest_line_generic(mouse_position, viewport, &RectangularGizmoType::all(), |t| {
|
||||
get_line_points_for_rect(t, column_index, row_index, spacing, stroke_width)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn closest_line_iso(mouse_position: DVec2, column_index: u32, row_index: u32, angles: DVec2, spacing: DVec2, viewport: DAffine2, stroke_width: Option<f64>) -> IsometricGizmoType {
|
||||
closest_line_generic(mouse_position, viewport, &IsometricGizmoType::all(), |t| {
|
||||
get_line_points_for_iso(t, column_index, row_index, angles, spacing, stroke_width)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user