Curve::bounds + ability to zoom to fit with delay (hack) for CurveEd

This commit is contained in:
Dmitry
2023-09-27 15:50:56 +03:00
parent 615d025f16
commit 88d66c7110
4 changed files with 185 additions and 112 deletions
+72 -1
View File
@@ -1,5 +1,5 @@
use crate::{
math::{cubicf, lerpf},
math::{cubicf, inf_sup_cubicf, lerpf, Rect},
reflect::prelude::*,
visitor::prelude::*,
};
@@ -243,6 +243,77 @@ impl Curve {
0.0
}
}
pub fn bounds(&self) -> Rect<f32> {
let mut max_y = -f32::MAX;
let mut min_y = f32::MAX;
let mut max_x = -f32::MAX;
let mut min_x = f32::MAX;
let mut push = |x: f32, y: f32| {
if x > max_x {
max_x = x;
}
if x < min_x {
min_x = x;
}
if y > max_y {
max_y = y;
}
if y < min_y {
min_y = y;
}
};
for keys in self.keys.windows(2) {
let left = &keys[0];
let right = &keys[1];
match (&left.kind, &right.kind) {
// Cubic-to-constant and cubic-to-linear is depicted as Hermite spline with right tangent == 0.0.
(
CurveKeyKind::Cubic {
right_tangent: left_tangent,
..
},
CurveKeyKind::Constant,
)
| (
CurveKeyKind::Cubic {
right_tangent: left_tangent,
..
},
CurveKeyKind::Linear,
) => {
let (y0, y1) = inf_sup_cubicf(left.value, right.value, *left_tangent, 0.0);
push(left.location, y0);
push(right.location, y1);
}
// Cubic-to-cubic is depicted as Hermite spline.
(
CurveKeyKind::Cubic {
right_tangent: left_tangent,
..
},
CurveKeyKind::Cubic {
left_tangent: right_tangent,
..
},
) => {
let (y0, y1) =
inf_sup_cubicf(left.value, right.value, *left_tangent, *right_tangent);
push(left.location, y0);
push(right.location, y1);
}
_ => {
push(left.location, left.value);
push(right.location, right.value);
}
}
}
Rect::new(min_x, min_y, max_x - min_x, max_y - min_y)
}
}
#[cfg(test)]
+42
View File
@@ -1,6 +1,7 @@
//! Flying camera controller script is used to create flying cameras, that can be rotated via mouse and moved via keyboard keys.
//! See [`FlyingCameraController`] docs for more info and usage examples.
use fyrox::core::curve::{Curve, CurveKey, CurveKeyKind};
use fyrox::{
core::{
algebra::{UnitQuaternion, Vector3},
@@ -39,6 +40,9 @@ pub struct FlyingCameraController {
pub move_left_key: InheritableVariable<KeyBinding>,
pub move_right_key: InheritableVariable<KeyBinding>,
pub acceleration_curve: InheritableVariable<Curve>,
pub deceleration_curve: InheritableVariable<Curve>,
#[reflect(hidden)]
#[visit(skip)]
move_forward: bool,
@@ -66,6 +70,44 @@ impl Default for FlyingCameraController {
move_backward_key: KeyBinding::Some(KeyCode::KeyS).into(),
move_left_key: KeyBinding::Some(KeyCode::KeyA).into(),
move_right_key: KeyBinding::Some(KeyCode::KeyD).into(),
acceleration_curve: Curve::from(vec![
CurveKey::new(
0.0,
0.0,
CurveKeyKind::Cubic {
left_tangent: 0.0,
right_tangent: 0.0,
},
),
CurveKey::new(
1.0,
1.0,
CurveKeyKind::Cubic {
left_tangent: 0.0,
right_tangent: 0.0,
},
),
])
.into(),
deceleration_curve: Curve::from(vec![
CurveKey::new(
0.0,
1.0,
CurveKeyKind::Cubic {
left_tangent: 0.0,
right_tangent: 0.0,
},
),
CurveKey::new(
1.0,
0.0,
CurveKeyKind::Cubic {
left_tangent: 0.0,
right_tangent: 0.0,
},
),
])
.into(),
move_forward: false,
move_backward: false,
move_left: false,
+55 -102
View File
@@ -4,7 +4,7 @@ use crate::{
algebra::{Matrix3, Point2, SimdPartialOrd, Vector2, Vector3},
color::Color,
curve::{Curve, CurveKeyKind},
math::{cubicf, inf_sup_cubicf, lerpf, wrap_angle, Rect},
math::{cubicf, lerpf, wrap_angle, Rect},
pool::Handle,
uuid::Uuid,
},
@@ -23,6 +23,7 @@ use crate::{
BuildContext, Control, RcUiNodeHandle, Thickness, UiNode, UserInterface, VerticalAlignment,
};
use fxhash::FxHashSet;
use std::sync::mpsc::Sender;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
@@ -36,7 +37,11 @@ pub enum CurveEditorMessage {
Sync(Curve),
ViewPosition(Vector2<f32>),
Zoom(Vector2<f32>),
ZoomToFit,
ZoomToFit {
/// Should the zoom to fit be performed on some of the next update cycle (up to 10 frames delay), or immediately when
/// processing the message.
after_layout: bool,
},
HighlightZones(Vec<HighlightZone>),
// Internal messages. Use only when you know what you're doing.
@@ -54,7 +59,7 @@ impl CurveEditorMessage {
define_constructor!(CurveEditorMessage:Sync => fn sync(Curve), layout: false);
define_constructor!(CurveEditorMessage:ViewPosition => fn view_position(Vector2<f32>), layout: false);
define_constructor!(CurveEditorMessage:Zoom => fn zoom(Vector2<f32>), layout: false);
define_constructor!(CurveEditorMessage:ZoomToFit => fn zoom_to_fit(), layout: false);
define_constructor!(CurveEditorMessage:ZoomToFit => fn zoom_to_fit(after_layout: bool), layout: true);
define_constructor!(CurveEditorMessage:HighlightZones => fn hightlight_zones(Vec<HighlightZone>), layout: false);
// Internal. Use only when you know what you're doing.
define_constructor!(CurveEditorMessage:RemoveSelection => fn remove_selection(), layout: false);
@@ -103,6 +108,7 @@ pub struct CurveEditor {
min_zoom: Vector2<f32>,
max_zoom: Vector2<f32>,
highlight_zones: Vec<HighlightZone>,
zoom_to_fit_timer: Option<usize>,
}
crate::define_widget_deref!(CurveEditor);
@@ -479,106 +485,14 @@ impl Control for CurveEditor {
self.sort_keys();
self.send_curve(ui);
}
CurveEditorMessage::ZoomToFit => {
let mut max_y = -f32::MAX;
let mut min_y = f32::MAX;
let mut max_x = -f32::MAX;
let mut min_x = f32::MAX;
let mut push = |x: f32, y: f32| {
if x > max_x {
max_x = x;
}
if x < min_x {
min_x = x;
}
if y > max_y {
max_y = y;
}
if y < min_y {
min_y = y;
}
};
for keys in self.key_container.keys().windows(2) {
let left = &keys[0];
let right = &keys[1];
match (&left.kind, &right.kind) {
// Cubic-to-constant and cubic-to-linear is depicted as Hermite spline with right tangent == 0.0.
(
CurveKeyKind::Cubic {
right_tangent: left_tangent,
..
},
CurveKeyKind::Constant,
)
| (
CurveKeyKind::Cubic {
right_tangent: left_tangent,
..
},
CurveKeyKind::Linear,
) => {
let (y0, y1) = inf_sup_cubicf(
left.position.y,
right.position.y,
*left_tangent,
0.0,
);
push(left.position.x, y0);
push(right.position.x, y1);
}
// Cubic-to-cubic is depicted as Hermite spline.
(
CurveKeyKind::Cubic {
right_tangent: left_tangent,
..
},
CurveKeyKind::Cubic {
left_tangent: right_tangent,
..
},
) => {
let (y0, y1) = inf_sup_cubicf(
left.position.y,
right.position.y,
*left_tangent,
*right_tangent,
);
push(left.position.x, y0);
push(right.position.x, y1);
}
_ => {
push(left.position.x, left.position.y);
push(right.position.x, right.position.y);
}
}
CurveEditorMessage::ZoomToFit { after_layout } => {
if *after_layout {
// TODO: Layout system could take up to 10 frames in worst cases. This is super hackish solution
// but when it works, who cares.
self.zoom_to_fit_timer = Some(10);
} else {
self.zoom_to_fit(&ui.sender);
}
let min = Vector2::new(min_x, min_y);
let max = Vector2::new(max_x, max_y);
let center = (min + max).scale(0.5);
ui.send_message(CurveEditorMessage::zoom(
self.handle,
MessageDirection::ToWidget,
Vector2::new(
self.actual_local_size().x
/ (max.x - min.x).max(5.0 * f32::EPSILON),
self.actual_local_size().y
/ (max.y - min.y).max(5.0 * f32::EPSILON),
),
));
ui.send_message(CurveEditorMessage::view_position(
self.handle,
MessageDirection::ToWidget,
Vector2::new(
self.actual_local_size().x * 0.5 - center.x,
-self.actual_local_size().y * 0.5 + center.y,
),
));
}
CurveEditorMessage::ChangeSelectedKeysValue(value) => {
self.change_selected_keys_value(*value, ui);
@@ -634,6 +548,7 @@ impl Control for CurveEditor {
ui.send_message(CurveEditorMessage::zoom_to_fit(
self.handle,
MessageDirection::ToWidget,
false,
));
}
} else if let Some(NumericUpDownMessage::<f32>::Value(value)) = message.data() {
@@ -654,6 +569,16 @@ impl Control for CurveEditor {
}
}
}
fn update(&mut self, _dt: f32, sender: &Sender<UiMessage>) {
if let Some(timer) = self.zoom_to_fit_timer.as_mut() {
*timer = timer.saturating_sub(1);
if *timer == 0 {
self.zoom_to_fit(sender);
self.zoom_to_fit_timer = None;
}
}
}
}
fn draw_cubic(
@@ -697,6 +622,33 @@ impl CurveEditor {
});
}
fn zoom_to_fit(&mut self, sender: &Sender<UiMessage>) {
let bounds = self.key_container.curve().bounds();
let center = bounds.center();
sender
.send(CurveEditorMessage::zoom(
self.handle,
MessageDirection::ToWidget,
Vector2::new(
self.actual_local_size().x / bounds.w().max(5.0 * f32::EPSILON),
self.actual_local_size().y / bounds.h().max(5.0 * f32::EPSILON),
),
))
.unwrap();
sender
.send(CurveEditorMessage::view_position(
self.handle,
MessageDirection::ToWidget,
Vector2::new(
self.actual_local_size().x * 0.5 - center.x,
-self.actual_local_size().y * 0.5 + center.y,
),
))
.unwrap();
}
fn update_matrices(&self) {
let vp = Vector2::new(self.view_position.x, -self.view_position.y);
self.view_matrix.set(
@@ -1484,6 +1436,7 @@ impl CurveEditorBuilder {
min_zoom: self.min_zoom,
max_zoom: self.max_zoom,
highlight_zones: self.highlight_zones,
zoom_to_fit_timer: None,
};
ctx.add_node(UiNode::new(editor))
+16 -9
View File
@@ -27,15 +27,22 @@ impl PropertyEditorDefinition for CurvePropertyEditorDefinition {
ctx: PropertyEditorBuildContext,
) -> Result<PropertyEditorInstance, InspectorError> {
let value = ctx.property_info.cast_value::<Curve>()?;
Ok(PropertyEditorInstance::Simple {
editor: CurveEditorBuilder::new(
WidgetBuilder::new()
.with_min_size(Vector2::new(0.0, 200.0))
.with_margin(Thickness::uniform(1.0)),
)
.with_curve(value.clone())
.build(ctx.build_context),
})
let editor = CurveEditorBuilder::new(
WidgetBuilder::new()
.with_min_size(Vector2::new(0.0, 200.0))
.with_margin(Thickness::uniform(1.0)),
)
.with_curve(value.clone())
.build(ctx.build_context);
ctx.build_context
.sender()
.send(CurveEditorMessage::zoom_to_fit(
editor,
MessageDirection::ToWidget,
true,
))
.unwrap();
Ok(PropertyEditorInstance::Simple { editor })
}
fn create_message(