|
|
|
@ -1,51 +1,266 @@
|
|
|
|
|
use egui::{emath, Rect, Sense};
|
|
|
|
|
use bytemuck::Contiguous;
|
|
|
|
|
use egui::{DragValue, Rect, Sense, Ui, emath};
|
|
|
|
|
use epaint::{Color32, Shape, Stroke};
|
|
|
|
|
use log::info;
|
|
|
|
|
use flare_shader::Coefs;
|
|
|
|
|
|
|
|
|
|
pub struct TransformEditor {
|
|
|
|
|
transforms: Vec<Coefs>
|
|
|
|
|
pub const TRANSFORM_POINT_RADIUS: f32 = 0.03;
|
|
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
|
|
|
pub enum TransformElement {
|
|
|
|
|
Origin,
|
|
|
|
|
X,
|
|
|
|
|
Y,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn coef_to_shapes(coef: &Coefs, to_screen: emath::RectTransform) -> Vec<Shape> {
|
|
|
|
|
let origin = to_screen.transform_pos(egui::pos2(coef.c, -coef.f));
|
|
|
|
|
let x = to_screen.transform_pos(egui::pos2(coef.c + coef.a, -(coef.f + coef.b)));
|
|
|
|
|
let y = to_screen.transform_pos(egui::pos2(coef.c + coef.d, -(coef.f + coef.e)));
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
|
pub struct Transform {
|
|
|
|
|
origin: egui::Pos2,
|
|
|
|
|
x: egui::Pos2,
|
|
|
|
|
y: egui::Pos2,
|
|
|
|
|
active_element: Option<TransformElement>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let stroke = Stroke::new(2.0, Color32::BLUE);
|
|
|
|
|
let translucent = Color32::from_rgba_unmultiplied(0, 0, 255, 8);
|
|
|
|
|
impl Transform {
|
|
|
|
|
pub fn check_active(&mut self, hover_pos: Option<egui::Pos2>, drag_delta: egui::Vec2) -> bool {
|
|
|
|
|
if hover_pos.is_none() {
|
|
|
|
|
self.active_element.take();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let shapes = vec![
|
|
|
|
|
Shape::circle_stroke(origin, 5.0, stroke),
|
|
|
|
|
Shape::circle_stroke(x, 5.0, stroke),
|
|
|
|
|
Shape::circle_stroke(y, 5.0, stroke),
|
|
|
|
|
Shape::convex_polygon(vec![origin, x, y], translucent, stroke),
|
|
|
|
|
];
|
|
|
|
|
let hover_pos = hover_pos.unwrap();
|
|
|
|
|
self.active_element = if test_point_in_circle(hover_pos, self.x + drag_delta, TRANSFORM_POINT_RADIUS * 2.0) {
|
|
|
|
|
Some(TransformElement::X)
|
|
|
|
|
} else if test_point_in_circle(hover_pos, self.y + drag_delta, TRANSFORM_POINT_RADIUS * 2.0) {
|
|
|
|
|
Some(TransformElement::Y)
|
|
|
|
|
} else if test_point_in_circle(hover_pos, self.origin + drag_delta, TRANSFORM_POINT_RADIUS * 2.0)
|
|
|
|
|
|| test_point_in_triangle(hover_pos, self.origin + drag_delta, self.x + drag_delta, self.y + drag_delta)
|
|
|
|
|
{
|
|
|
|
|
Some(TransformElement::Origin)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
shapes
|
|
|
|
|
self.active_element.is_some()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn drag_update(&mut self, drag_delta: egui::Vec2) {
|
|
|
|
|
if let Some(active_element) = self.active_element {
|
|
|
|
|
match active_element {
|
|
|
|
|
TransformElement::X => self.x += drag_delta,
|
|
|
|
|
TransformElement::Y => self.y += drag_delta,
|
|
|
|
|
TransformElement::Origin => {
|
|
|
|
|
self.origin += drag_delta;
|
|
|
|
|
self.x += drag_delta;
|
|
|
|
|
self.y += drag_delta;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui_draw(&self, painter: &egui::Painter, to_screen: emath::RectTransform, drag_delta: egui::Vec2) {
|
|
|
|
|
let color_active = Color32::from_rgba_unmultiplied(0, 0, u8::MAX_VALUE, 8);
|
|
|
|
|
|
|
|
|
|
let stroke = Stroke::new(2.0, Color32::BLUE);
|
|
|
|
|
|
|
|
|
|
let origin_ifs = match self.active_element {
|
|
|
|
|
Some(TransformElement::Origin) => self.origin + drag_delta,
|
|
|
|
|
_ => self.origin
|
|
|
|
|
};
|
|
|
|
|
let origin_screen = to_screen.transform_pos(origin_ifs);
|
|
|
|
|
|
|
|
|
|
let x_ifs = match self.active_element {
|
|
|
|
|
Some(TransformElement::X | TransformElement::Origin) => self.x + drag_delta,
|
|
|
|
|
_ => self.x
|
|
|
|
|
};
|
|
|
|
|
let x_screen = to_screen.transform_pos(x_ifs);
|
|
|
|
|
|
|
|
|
|
let y_ifs = match self.active_element {
|
|
|
|
|
Some(TransformElement::Y | TransformElement::Origin) => self.y + drag_delta,
|
|
|
|
|
_ => self.y
|
|
|
|
|
};
|
|
|
|
|
let y_screen = to_screen.transform_pos(y_ifs);
|
|
|
|
|
|
|
|
|
|
let body_color = if self.active_element.is_some() {
|
|
|
|
|
color_active
|
|
|
|
|
} else {
|
|
|
|
|
Color32::TRANSPARENT
|
|
|
|
|
};
|
|
|
|
|
painter.add(Shape::convex_polygon(
|
|
|
|
|
vec![origin_screen, x_screen, y_screen],
|
|
|
|
|
body_color,
|
|
|
|
|
stroke,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
let point_radius = to_screen.scale().min_elem() * TRANSFORM_POINT_RADIUS;
|
|
|
|
|
|
|
|
|
|
let origin_color = match self.active_element {
|
|
|
|
|
Some(TransformElement::Origin) => color_active,
|
|
|
|
|
_ => Color32::TRANSPARENT,
|
|
|
|
|
};
|
|
|
|
|
painter.add(Shape::circle_stroke(origin_screen, point_radius, stroke));
|
|
|
|
|
painter.add(Shape::circle_filled(
|
|
|
|
|
origin_screen,
|
|
|
|
|
point_radius,
|
|
|
|
|
origin_color,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
let x_color = match self.active_element {
|
|
|
|
|
Some(TransformElement::X) => color_active,
|
|
|
|
|
_ => Color32::TRANSPARENT,
|
|
|
|
|
};
|
|
|
|
|
painter.add(Shape::circle_stroke(x_screen, point_radius, stroke));
|
|
|
|
|
painter.add(Shape::circle_filled(x_screen, point_radius, x_color));
|
|
|
|
|
|
|
|
|
|
let y_color = match self.active_element {
|
|
|
|
|
Some(TransformElement::X) => color_active,
|
|
|
|
|
_ => Color32::TRANSPARENT,
|
|
|
|
|
};
|
|
|
|
|
painter.add(Shape::circle_stroke(y_screen, point_radius, stroke));
|
|
|
|
|
painter.add(Shape::circle_filled(y_screen, point_radius, y_color));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for Transform {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Transform {
|
|
|
|
|
origin: egui::pos2(0.0, 0.0),
|
|
|
|
|
x: egui::pos2(1.0, 0.0),
|
|
|
|
|
y: egui::pos2(0.0, -1.0),
|
|
|
|
|
active_element: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
|
pub struct TransformEditor {
|
|
|
|
|
transforms: Vec<Transform>,
|
|
|
|
|
hover_index: Option<usize>,
|
|
|
|
|
hover_pos: Option<egui::Pos2>,
|
|
|
|
|
drag_index: Option<usize>,
|
|
|
|
|
drag_start_pos: Option<egui::Pos2>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn test_point_in_triangle(pt: egui::Pos2, v1: egui::Pos2, v2: egui::Pos2, v3: egui::Pos2) -> bool {
|
|
|
|
|
puffin::profile_function!();
|
|
|
|
|
|
|
|
|
|
// https://stackoverflow.com/a/2049593
|
|
|
|
|
let sign = |p1: egui::Pos2, p2: egui::Pos2, p3: egui::Pos2| -> f32 {
|
|
|
|
|
(p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let d1 = sign(pt, v1, v2);
|
|
|
|
|
let d2 = sign(pt, v2, v3);
|
|
|
|
|
let d3 = sign(pt, v3, v1);
|
|
|
|
|
|
|
|
|
|
let has_neg = [d1, d2, d3].iter().any(|v| *v < 0.0);
|
|
|
|
|
let has_pos = [d1, d2, d3].iter().any(|v| *v > 0.0);
|
|
|
|
|
|
|
|
|
|
!(has_neg && has_pos)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn test_point_in_circle(pt: egui::Pos2, center: egui::Pos2, radius: f32) -> bool {
|
|
|
|
|
((pt.x - center.x).powf(2.0) + (pt.y - center.y).powf(2.0)) < radius.powf(2.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TransformEditor {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
let mut editor = Self { transforms: vec![] };
|
|
|
|
|
editor.add_transform(Coefs::IDENTITY);
|
|
|
|
|
let mut editor = TransformEditor::default();
|
|
|
|
|
editor.add_transform();
|
|
|
|
|
|
|
|
|
|
editor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn add_transform(&mut self, coefs: Coefs) {
|
|
|
|
|
self.transforms.push(coefs);
|
|
|
|
|
pub fn add_transform(&mut self) {
|
|
|
|
|
self.transforms.push(Default::default());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
|
self.transforms.len()
|
|
|
|
|
fn check_active(&mut self, hover_pos: Option<egui::Pos2>) {
|
|
|
|
|
// Find the active transform; the previously active transform has priority
|
|
|
|
|
let drag_delta = self.drag_delta();
|
|
|
|
|
if let Some(hover_index) = self.hover_index {
|
|
|
|
|
if self.transforms[hover_index].check_active(hover_pos, drag_delta) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i, transform) in self.transforms.iter_mut().enumerate() {
|
|
|
|
|
if transform.check_active(hover_pos, drag_delta) {
|
|
|
|
|
self.hover_index = Some(i);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.hover_index.take();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
|
|
|
|
let (response, painter) = ui.allocate_painter(ui.available_size(), Sense::hover());
|
|
|
|
|
fn drag_delta(&self) -> egui::Vec2 {
|
|
|
|
|
if let (Some(hover_pos), Some(drag_start_pos)) = (self.hover_pos, self.drag_start_pos) {
|
|
|
|
|
hover_pos - drag_start_pos
|
|
|
|
|
} else {
|
|
|
|
|
egui::Vec2::ZERO
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui_update(
|
|
|
|
|
&mut self,
|
|
|
|
|
hover_pos: Option<egui::Pos2>,
|
|
|
|
|
drag_delta: egui::Vec2,
|
|
|
|
|
clicked: bool,
|
|
|
|
|
drag_started: bool,
|
|
|
|
|
drag_ended: bool,
|
|
|
|
|
) {
|
|
|
|
|
if self.transforms.is_empty() {
|
|
|
|
|
self.hover_index.take();
|
|
|
|
|
self.drag_index.take();
|
|
|
|
|
self.drag_start_pos.take();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.hover_pos = hover_pos;
|
|
|
|
|
self.check_active(hover_pos);
|
|
|
|
|
|
|
|
|
|
if clicked || drag_started {
|
|
|
|
|
self.drag_index = self.hover_index;
|
|
|
|
|
self.drag_start_pos = Some(hover_pos.unwrap() - drag_delta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if drag_ended {
|
|
|
|
|
if let Some(drag_index) = self.drag_index {
|
|
|
|
|
let drag_delta = self.drag_delta();
|
|
|
|
|
info!("Applying drag delta {:?}", drag_delta);
|
|
|
|
|
self.transforms[drag_index].drag_update(drag_delta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.drag_index.take();
|
|
|
|
|
self.drag_start_pos.take();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui_draw(&mut self, painter: &egui::Painter, to_screen: emath::RectTransform) {
|
|
|
|
|
let drag_delta = if let (Some(hover_pos), Some(drag_start_pos)) = (self.hover_pos, self.drag_start_pos) {
|
|
|
|
|
hover_pos - drag_start_pos
|
|
|
|
|
} else {
|
|
|
|
|
egui::Vec2::ZERO
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (index, transform) in self.transforms.iter().enumerate() {
|
|
|
|
|
// Active transform is painted at the end to maintain priority
|
|
|
|
|
if self.hover_index.map_or(false, |i| i == index) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transform.ui_draw(painter, to_screen, drag_delta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.hover_index.map(|i| self.transforms[i].ui_draw(painter, to_screen, drag_delta));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) -> egui::Response {
|
|
|
|
|
let (response, painter) = ui.allocate_painter(ui.available_size(), Sense::click_and_drag());
|
|
|
|
|
|
|
|
|
|
// Step one: set up conversions between screen space and IFS space coordinates
|
|
|
|
|
let interact_rect = response.interact_rect;
|
|
|
|
|
|
|
|
|
|
// Aspect-ratio scaling; minimum dimension will be [-2.0, 2.0]
|
|
|
|
|
let interact_max_dim = interact_rect.width().max(interact_rect.height());
|
|
|
|
|
let interact_min_dim = interact_rect.width().min(interact_rect.height());
|
|
|
|
|
let interact_max_is_width = interact_max_dim == interact_rect.width();
|
|
|
|
@ -59,13 +274,30 @@ impl TransformEditor {
|
|
|
|
|
|
|
|
|
|
let transform_area = Rect::from_min_max(ifs_min, ifs_min * -1.0);
|
|
|
|
|
let to_screen = emath::RectTransform::from_to(transform_area, response.interact_rect);
|
|
|
|
|
let to_ifs = emath::RectTransform::from_to(response.interact_rect, transform_area);
|
|
|
|
|
|
|
|
|
|
self.transforms.iter().map(|coef| {
|
|
|
|
|
coef_to_shapes(coef, to_screen)
|
|
|
|
|
}).flat_map(|x| x).for_each(|shape| {
|
|
|
|
|
let _ = painter.add(shape);
|
|
|
|
|
egui::TopBottomPanel::bottom("response_stats").show(ctx, |ui| {
|
|
|
|
|
let hover_pos_string = response.hover_pos().map_or_else(|| "None".to_owned(), |p| {
|
|
|
|
|
let ifs_pos = to_ifs.transform_pos(p);
|
|
|
|
|
format!("({} {})", ifs_pos.x, ifs_pos.y)
|
|
|
|
|
});
|
|
|
|
|
ui.label(format!("Hover Pos: {}", hover_pos_string));
|
|
|
|
|
ui.label(format!("Hover Index: {:?}", self.hover_index));
|
|
|
|
|
ui.label(format!("Drag Index: {:?}", self.drag_index));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Step two: update internal state based on recent interactions
|
|
|
|
|
self.ui_update(
|
|
|
|
|
response.hover_pos().map(|v| to_ifs.transform_pos(v)),
|
|
|
|
|
response.drag_delta() / interact_max_dim,
|
|
|
|
|
response.clicked(),
|
|
|
|
|
response.drag_started(),
|
|
|
|
|
response.drag_stopped(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Step three: draw the transforms
|
|
|
|
|
self.ui_draw(&painter, to_screen);
|
|
|
|
|
|
|
|
|
|
response
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|