diff --git a/crates/ctl-core/src/model/collider/mod.rs b/crates/ctl-core/src/model/collider/mod.rs index 169488e1..e65ca835 100644 --- a/crates/ctl-core/src/model/collider/mod.rs +++ b/crates/ctl-core/src/model/collider/mod.rs @@ -12,7 +12,7 @@ pub struct Collision { pub penetration: Coord, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Collider { pub position: vec2, pub rotation: Angle, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 1f19ebe4..fb27ff75 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -818,16 +818,24 @@ impl LevelEditor { d <= MAX_VISIBILITY }; + // TODO: use cached + let curve = light_event.light.movement.bake(); let mut points: Vec<_> = light_event .light .movement .timed_positions() - .map(|(i, trans, time)| { + .map(|(i, trans_control, time)| { + let trans_actual = match i { + WaypointId::Initial => curve.get(0, Time::ZERO), + WaypointId::Frame(i) => curve.get(i, Time::ONE), + } + .unwrap_or(trans_control); // Should be unreachable, but just in case ( Waypoint { visible: visible(time), original: Some(i), - collider: base_collider.transformed(trans), + control: base_collider.transformed(trans_control), + actual: base_collider.transformed(trans_actual), }, time, ) @@ -835,8 +843,8 @@ impl LevelEditor { .collect(); points.sort_by_key(|(point, time)| { ( - point.collider.position.x, - point.collider.position.y, + point.control.position.x, + point.control.position.y, (event_time + *time - self.current_beat).abs(), ) }); @@ -844,9 +852,9 @@ impl LevelEditor { { let mut points = points.iter_mut(); if let Some(last) = points.next() { - let mut last = last.0.collider.position; + let mut last = last.0.control.position; for (point, _) in points { - let pos = point.collider.position; + let pos = point.control.position; if last == pos { point.visible = false; } @@ -863,17 +871,19 @@ impl LevelEditor { let i = match points.binary_search_by_key(&new_time, |(_, time)| *time) { Ok(i) | Err(i) => i, }; + let control = base_collider.transformed(Transform { + translation: cursor_world_pos_snapped, + rotation: self.place_rotation, + scale: self.place_scale, + }); points.insert( i, ( Waypoint { visible: true, original: None, - collider: base_collider.transformed(Transform { - translation: cursor_world_pos_snapped, - rotation: self.place_rotation, - scale: self.place_scale, - }), + actual: control.clone(), + control, }, new_time, ), @@ -883,7 +893,9 @@ impl LevelEditor { let points: Vec<_> = points.into_iter().map(|(point, _)| point).collect(); let hovered = points.iter().position(|point| { - point.visible && point.collider.contains(cursor_world_pos) + point.visible + && (point.control.contains(cursor_world_pos) + || point.actual.contains(cursor_world_pos)) }); waypoints = Some(Waypoints { diff --git a/src/editor/state.rs b/src/editor/state.rs index 1b0c6a63..04f61003 100644 --- a/src/editor/state.rs +++ b/src/editor/state.rs @@ -54,7 +54,8 @@ pub struct Waypoint { /// Index of the original keyframe. /// `None` when placing a new waypoint. pub original: Option, - pub collider: Collider, + pub control: Collider, + pub actual: Collider, } impl EditorLevelState { diff --git a/src/render/editor/game.rs b/src/render/editor/game.rs index 8efa3fb8..438a0fbd 100644 --- a/src/render/editor/game.rs +++ b/src/render/editor/game.rs @@ -276,8 +276,45 @@ impl EditorRender { continue; } + if point.actual != point.control { + self.util.draw_outline( + &point.control, + 0.025, + crate::util::with_alpha(color, alpha), + &level_editor.model.camera, + &mut pixel_buffer, + ); + + // Connect control and actual with a line + let color = crate::util::with_alpha(color, alpha * 0.75); + let mut from = draw2d::ColoredVertex { + a_pos: point.control.position.as_f32(), + a_color: color, + }; + let to = draw2d::ColoredVertex { + a_pos: point.actual.position.as_f32(), + a_color: color, + }; + + let period = options.dash_length + options.space_length; + let speed = 1.0; + let t = ((level_editor.real_time.as_f32() * speed) / period) + .fract() + * period; + from.a_pos += (to.a_pos - from.a_pos).normalize_or_zero() * t; + self.util.draw_dashed_chain( + &[from, to], + &util::DashRenderOptions { + width: options.width * 0.5, + ..options + }, + &level_editor.model.camera, + &mut pixel_buffer, + ); + } + self.util.draw_outline( - &point.collider, + &point.actual, 0.05, crate::util::with_alpha(color, alpha), &level_editor.model.camera, @@ -286,7 +323,7 @@ impl EditorRender { let text_color = crate::util::with_alpha(THEME.light, alpha); self.util.draw_text_with( format!("{}", i + 1), - point.collider.position, + point.control.position, TextRenderOptions::new(1.5).color(text_color), Some(util::additive()), &level_editor.model.camera, @@ -304,7 +341,7 @@ impl EditorRender { if let Some(beat) = beat_time { self.util.draw_text_with( format!("at {}", beat), - point.collider.position - vec2(0.0, 0.6).as_r32(), + point.control.position - vec2(0.0, 0.6).as_r32(), TextRenderOptions::new(0.6).color(text_color), Some(util::additive()), &level_editor.model.camera,