Skip to content

Commit

Permalink
[editor] waypoint interpolation
Browse files Browse the repository at this point in the history
  • Loading branch information
Nertsal committed Oct 17, 2024
1 parent 903f58e commit 4114bd2
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 27 deletions.
1 change: 0 additions & 1 deletion crates/ctl-core/src/legacy/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ impl From<Level> for crate::Level {
fade_in: light.light.movement.fade_in,
fade_out: light.light.movement.fade_out,
initial: light.light.movement.initial.into(),
curve: crate::TrajectoryInterpolation::default(),
key_frames: light
.light
.movement
Expand Down
31 changes: 18 additions & 13 deletions crates/ctl-core/src/model/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ pub struct Movement {
/// Time (in beats) to spend fading out of the last keyframe.
pub fade_out: Time,
pub initial: Transform,
#[serde(default)]
pub curve: TrajectoryInterpolation,
pub key_frames: VecDeque<MoveFrame>,
}

Expand All @@ -17,10 +15,10 @@ pub struct MoveFrame {
/// How long (in beats) should the interpolation from the last frame to that frame last.
pub lerp_time: Time,
/// Interpolation to use when moving towards this frame.
#[serde(default)]
pub interpolation: MoveInterpolation,
/// Whether to start a new curve starting from this frame.
/// Is set to `None`, the curve will either continue the previous type,
/// or continue as linear in the case of bezier.
/// Whether to start a new curve going towards this frame and further.
/// Is set to `None`, the curve will either continue the previous type.
pub change_curve: Option<TrajectoryInterpolation>,
pub transform: Transform,
}
Expand Down Expand Up @@ -171,7 +169,6 @@ impl Default for Movement {
fade_in: r32(1.0),
fade_out: r32(1.0),
initial: Transform::default(),
curve: TrajectoryInterpolation::default(),
key_frames: VecDeque::new(),
}
}
Expand Down Expand Up @@ -205,10 +202,16 @@ impl Movement {
}
}

pub fn get_curve(&self, id: WaypointId) -> Option<Option<TrajectoryInterpolation>> {
pub fn get_interpolation(
&self,
id: WaypointId,
) -> Option<(MoveInterpolation, Option<TrajectoryInterpolation>)> {
match id {
WaypointId::Initial => Some(Some(self.curve)),
WaypointId::Frame(i) => self.key_frames.get(i).map(|frame| frame.change_curve),
WaypointId::Initial => None,
WaypointId::Frame(i) => self
.key_frames
.get(i)
.map(|frame| (frame.interpolation, frame.change_curve)),
}
}

Expand Down Expand Up @@ -321,7 +324,7 @@ impl Movement {
pub fn bake(&self) -> Interpolation<Transform> {
bake_movement(
self.initial,
self.curve,
TrajectoryInterpolation::default(),
self.frames_iter()
.map(|frame| (frame.transform, frame.change_curve)),
)
Expand All @@ -335,7 +338,7 @@ pub fn bake_movement<T: 'static + Interpolatable>(
) -> Interpolation<T> {
let points = std::iter::once((initial, None)).chain(keyframes);

let mk_segment = |curve, segment: &[_]| match curve {
let mk_segment = |curve, segment: &[T]| match curve {
TrajectoryInterpolation::Linear => InterpolationSegment::linear(segment),
TrajectoryInterpolation::Spline { tension } => {
InterpolationSegment::spline(segment, tension.as_f32())
Expand All @@ -347,13 +350,15 @@ pub fn bake_movement<T: 'static + Interpolatable>(
let mut current_curve = initial_curve;
let mut current_segment = vec![]; // TODO: smallvec
for (point, curve) in points {
current_segment.push(point.clone());
if let Some(new_curve) = curve {
if !current_segment.is_empty() {
segments.push(mk_segment(current_curve, &current_segment));
current_segment = vec![current_segment.last().unwrap().clone()];
}
current_segment = vec![point];
current_segment.push(point);
current_curve = new_curve;
} else {
current_segment.push(point);
}
}

Expand Down
38 changes: 34 additions & 4 deletions src/editor/action/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum LevelAction {
RotateWaypoint(LightId, WaypointId, Angle<Coord>),
ScaleWaypoint(LightId, WaypointId, Coord),
SetWaypointFrame(LightId, WaypointId, Transform),
SetWaypointInterpolation(LightId, WaypointId, MoveInterpolation),
SetWaypointCurve(LightId, WaypointId, Option<TrajectoryInterpolation>),
MoveWaypoint(LightId, WaypointId, Change<vec2<Coord>>),
}
Expand Down Expand Up @@ -100,6 +101,7 @@ impl LevelAction {
LevelAction::SetName(_) => false,
LevelAction::SetWaypointFrame(..) => false,
LevelAction::SetWaypointCurve(..) => false,
LevelAction::SetWaypointInterpolation(..) => false,
LevelAction::MoveLight(_, time, position) => {
time.is_noop(&Time::ZERO) && position.is_noop(&vec2::ZERO)
}
Expand Down Expand Up @@ -224,6 +226,9 @@ impl LevelEditor {
LevelAction::SetWaypointCurve(light, waypoint, curve) => {
self.set_waypoint_curve(light, waypoint, curve)
}
LevelAction::SetWaypointInterpolation(light, waypoint, interpolation) => {
self.set_waypoint_interpolation(light, waypoint, interpolation)
}
LevelAction::MoveLight(light, time, pos) => self.move_light(light, time, pos),
LevelAction::MoveWaypoint(light, waypoint, pos) => {
self.move_waypoint(light, waypoint, pos)
Expand Down Expand Up @@ -298,10 +303,7 @@ impl LevelEditor {

match waypoint_id {
WaypointId::Initial => {
if let Some(curve) = curve {
event.light.movement.curve = curve;
self.save_state(default());
}
// Invalid
}
WaypointId::Frame(frame) => {
let Some(frame) = event.light.movement.key_frames.get_mut(frame) else {
Expand All @@ -313,6 +315,34 @@ impl LevelEditor {
}
}

fn set_waypoint_interpolation(
&mut self,
light_id: LightId,
waypoint_id: WaypointId,
interpolation: MoveInterpolation,
) {
let Some(timed_event) = self.level.events.get_mut(light_id.event) else {
return;
};

let Event::Light(event) = &mut timed_event.event else {
return;
};

match waypoint_id {
WaypointId::Initial => {
// Invalid
}
WaypointId::Frame(frame) => {
let Some(frame) = event.light.movement.key_frames.get_mut(frame) else {
return;
};
frame.interpolation = interpolation;
self.save_state(default());
}
}
}

fn select_waypoint(&mut self, waypoint_id: WaypointId) {
let Some(light_id) = self.selected_light else {
return;
Expand Down
54 changes: 47 additions & 7 deletions src/editor/ui/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub struct EditorEditWidget {
/// Angle in degrees.
pub waypoint_angle: ValueWidget<f32>,
pub waypoint_curve: DropdownWidget<Option<TrajectoryInterpolation>>,
pub waypoint_interpolation: DropdownWidget<MoveInterpolation>,

pub current_beat: TextWidget,
pub timeline: TimelineWidget,
Expand Down Expand Up @@ -141,6 +142,16 @@ impl EditorEditWidget {
("Bezier", Some(TrajectoryInterpolation::Bezier)),
],
),
waypoint_interpolation: DropdownWidget::new(
"Interpolation",
0,
[
("Linear", MoveInterpolation::Linear),
("Smoothstep", MoveInterpolation::Smoothstep),
("EaseIn", MoveInterpolation::EaseIn),
("EaseOut", MoveInterpolation::EaseOut),
],
),

current_beat: TextWidget::default().aligned(vec2(0.5, 0.0)),
timeline: TimelineWidget::new(context.clone()),
Expand Down Expand Up @@ -195,8 +206,8 @@ impl StatefulWidget for EditorEditWidget {
let mut main = main
.extend_symmetric(-vec2(1.0, 2.0) * layout_size)
.extend_up(-layout_size);
let mut left_bar = main.cut_left(font_size * 5.0);
let mut right_bar = main.cut_right(font_size * 5.0);
let mut left_bar = main.cut_left(layout_size * 7.0);
let mut right_bar = main.cut_right(layout_size * 7.0);

let spacing = layout_size * 0.25;
let title_size = font_size * 1.3;
Expand Down Expand Up @@ -508,17 +519,45 @@ impl StatefulWidget for EditorEditWidget {
.into(),
);

let curve = bar.cut_top(value_height);
let curve = bar.cut_top(button_height);
bar.cut_top(spacing);
let interpolation = bar.cut_top(button_height);
bar.cut_top(spacing);
if let Some(mut value) = light.light.movement.get_curve(selected) {
if let Some((mut move_interpolation, mut curve_interpolation)) =
light.light.movement.get_interpolation(selected)
{
self.waypoint_curve.show();
self.waypoint_curve.update(curve, context, &mut value);
self.waypoint_curve.update(
curve,
context,
&mut curve_interpolation,
);
actions.push(
LevelAction::SetWaypointCurve(waypoints.light, selected, value)
.into(),
LevelAction::SetWaypointCurve(
waypoints.light,
selected,
curve_interpolation,
)
.into(),
);

self.waypoint_interpolation.show();
self.waypoint_interpolation.update(
interpolation,
context,
&mut move_interpolation,
);
actions.push(
LevelAction::SetWaypointInterpolation(
waypoints.light,
selected,
move_interpolation,
)
.into(),
);
} else {
self.waypoint_curve.hide();
self.waypoint_interpolation.hide();
}

if self.prev_waypoint.state.clicked {
Expand All @@ -541,6 +580,7 @@ impl StatefulWidget for EditorEditWidget {
self.waypoint_scale.hide();
self.waypoint_angle.hide();
self.waypoint_curve.hide();
self.waypoint_interpolation.hide();
}

{
Expand Down
1 change: 0 additions & 1 deletion src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ impl HoverButton {
fade_in: r32(0.0),
initial: Transform::scale(2.25),
key_frames: vec![MoveFrame::scale(0.5, 5.0), MoveFrame::scale(0.25, 75.0)].into(),
curve: TrajectoryInterpolation::Linear,
fade_out: r32(0.2),
},
clicked: false,
Expand Down
2 changes: 1 addition & 1 deletion src/render/editor/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ impl EditorRender {
};

// A dashed line moving through the waypoints to show general direction
const RESOLUTION: usize = 5;
const RESOLUTION: usize = 10;
// TODO: cache curve
let curve = event.light.movement.bake();
let mut positions: Vec<draw2d::ColoredVertex> = curve
Expand Down
6 changes: 6 additions & 0 deletions src/render/editor/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ impl EditorRender {
&mut self.mask,
framebuffer,
);
self.ui.draw_dropdown(
&ui.waypoint_interpolation,
self.font_size * 0.2,
&mut self.mask,
framebuffer,
);

{
// Timeline
Expand Down

0 comments on commit 4114bd2

Please sign in to comment.