Skip to content

Commit

Permalink
Add camera view editing mode (#100)
Browse files Browse the repository at this point in the history
* Add camera view editing mode

* changes suggested

* changes suggested
  • Loading branch information
khanghugo authored Jul 12, 2024
1 parent abee9c6 commit 42ca2bc
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 6 deletions.
5 changes: 4 additions & 1 deletion src/hooks/bxt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Bunnymod XT.

use std::ffi::CString;
use std::os::raw::{c_char, c_int};
use std::os::raw::{c_char, c_float, c_int};
use std::ptr::NonNull;

use hltas::HLTAS;
Expand All @@ -27,6 +27,8 @@ pub static BXT_TAS_NOREFRESH_UNTIL_LAST_FRAMES: Pointer<unsafe extern "C" fn() -
Pointer::empty(b"bxt_tas_norefresh_until_last_frames\0");
pub static BXT_TAS_STUDIO_NOREFRESH_OVERRIDE: Pointer<unsafe extern "C" fn(c_int)> =
Pointer::empty(b"bxt_tas_studio_norefresh_override\0");
pub static BXT_TAS_STUDIO_FREECAM_SET_ORIGIN: Pointer<unsafe extern "C" fn([c_float; 3])> =
Pointer::empty(b"bxt_tas_studio_freecam_set_origin\0");

static POINTERS: &[&dyn PointerTrait] = &[
&BXT_ON_TAS_PLAYBACK_FRAME,
Expand All @@ -37,6 +39,7 @@ static POINTERS: &[&dyn PointerTrait] = &[
&BXT_TAS_NEW,
&BXT_TAS_NOREFRESH_UNTIL_LAST_FRAMES,
&BXT_TAS_STUDIO_NOREFRESH_OVERRIDE,
&BXT_TAS_STUDIO_FREECAM_SET_ORIGIN,
];

#[cfg(unix)]
Expand Down
248 changes: 245 additions & 3 deletions src/modules/tas_studio/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ pub struct Editor {
/// Adjustment to insert a camera line.
insert_camera_line_adjustment: Option<InsertCameraLineAdjustment>,

/// Adjustment to a camera line depending on the context.
///
/// Adjusts the yaw of `target_yaw x` line.
///
/// Adjusts the change mode and final value of `change x to x over x` line.
camera_view_adjustment: Option<CameraViewAdjustment>,

/// Smoothing window size in seconds.
smooth_window_s: f32,
/// Smoothing small window size in seconds.
Expand Down Expand Up @@ -193,14 +200,20 @@ struct ExtraCameraEditorFrameData {
smoothing_input_region: Option<(usize, usize)>,
/// Index into `script.lines` of a change frame that ends on this frame.
change_line_that_ends_here: Vec<usize>,
/// Index of a frame where the change line which starts on this frame ends.
change_ends_at: Vec<usize>,
/// Index of a change line and a frame where the frame is end frame of change line.
change_ends_at: Vec<LineChangeEndsAt>,
/// Index into `script.lines` of a camera frame that starts or ends on this frame.
camera_line_that_starts_or_ends_here: Vec<usize>,
/// Index into `script.lines` of a camera frame that starts on this frame.
camera_line_that_starts_here: Vec<usize>,
}

#[derive(Debug, Default, Clone)]
struct LineChangeEndsAt {
line_idx: usize,
end_frame_idx: usize,
}

/// Data for handling adjustment done by pressing and dragging the mouse.
#[derive(Debug, Clone, Copy)]
struct MouseAdjustment<T> {
Expand Down Expand Up @@ -296,6 +309,22 @@ struct InsertCameraLineAdjustment {
did_split: bool,
}

/// `CameraViewAdjustmentMode::Alt` adjusts different things depending on the line type. For
/// target_yaw lines, it adjusts the velocity_lock angle.
#[derive(Debug, Clone, Copy)]
pub enum CameraViewAdjustmentMode {
Yaw,
Pitch,
Alt,
}

/// Data for handling camera view adjustment.
#[derive(Debug, Clone, Copy)]
pub struct CameraViewAdjustment {
pub mode: CameraViewAdjustmentMode,
camera_line_idx: usize,
}

#[derive(Debug, Default, Clone, Copy)]
pub struct KeyboardState {
/// Whether the "faster" key is pressed.
Expand Down Expand Up @@ -374,6 +403,14 @@ impl ManualOpError {
}
}

#[derive(Clone, Copy)]
pub struct Callbacks<'a> {
pub enable_mouse_look: &'a dyn Fn(),
pub disable_mouse_look: &'a dyn Fn(),
pub get_viewangles: &'a dyn Fn() -> [f32; 3],
pub change_view_origin: &'a dyn Fn(Vec3),
}

impl Editor {
pub fn open_db(mut db: Db) -> eyre::Result<Self> {
let branches = db.branches()?;
Expand Down Expand Up @@ -415,6 +452,7 @@ impl Editor {
first_shown_frame_idx: 0,
hovered_line_idx: None,
insert_camera_line_adjustment: None,
camera_view_adjustment: None,
smooth_window_s: 0.15,
smooth_small_window_s: 0.03,
smooth_small_window_multiplier: 3.,
Expand Down Expand Up @@ -482,6 +520,10 @@ impl Editor {
self.undo_log.len()
}

pub fn camera_view_adjustment(&self) -> Option<CameraViewAdjustment> {
self.camera_view_adjustment
}

pub fn set_in_camera_editor(&mut self, value: bool) {
if self.in_camera_editor == value {
return;
Expand Down Expand Up @@ -687,7 +729,10 @@ impl Editor {
.push(line_idx);
branch.extra_cam[frame_idx]
.change_ends_at
.push(end_frame_idx);
.push(LineChangeEndsAt {
line_idx,
end_frame_idx,
});
branch.extra_cam[end_frame_idx]
.camera_line_that_starts_or_ends_here
.push(line_idx);
Expand Down Expand Up @@ -726,6 +771,7 @@ impl Editor {
|| self.side_strafe_yawspeed_adjustment.is_some()
|| self.adjacent_side_strafe_yawspeed_adjustment.is_some()
|| self.insert_camera_line_adjustment.is_some()
|| self.camera_view_adjustment.is_some()
}

/// Updates the editor state.
Expand All @@ -736,6 +782,7 @@ impl Editor {
mouse: MouseState,
keyboard: KeyboardState,
deadline: Instant,
callbacks: Callbacks,
) -> eyre::Result<()> {
let _span = info_span!("Editor::tick").entered();

Expand All @@ -750,6 +797,7 @@ impl Editor {
self.tick_adjacent_side_strafe_yawspeed_adjustment(mouse, keyboard)?;

self.tick_insert_camera_line_adjustment(mouse, keyboard)?;
self.tick_camera_view_adjustment(mouse, self.prev_mouse_state, callbacks)?;

// Predict any frames that need prediction.
//
Expand Down Expand Up @@ -1229,6 +1277,71 @@ impl Editor {
});
}
}

if let Some(hovered_line_idx) = self.hovered_line_idx {
// For camera editor camera line editing.
if mouse.buttons.is_right_down() && !self.prev_mouse_state.buttons.is_right_down() {
assert!(self.camera_view_adjustment.is_none());
assert!(!self.is_any_adjustment_active());

let line = &self.branch().branch.script.lines[hovered_line_idx];

// Find the end frame of change line or camera line so we can change origin.
// In case of change line we need to know the end frame.
// Camera line we can just use the `line_first_frame_idx`.
let line_first_frame_idx = line_first_frame_idx(self.script())
.nth(hovered_line_idx)
.expect("invalid line index");

let change_line_end_frame_index = &self.branch().extra_cam
[line_first_frame_idx]
.change_ends_at
.iter()
.find(|LineChangeEndsAt { line_idx, .. }| *line_idx == hovered_line_idx)
.map(|LineChangeEndsAt { end_frame_idx, .. }| *end_frame_idx);

let end_frame_index =
if let Some(change_line_end_frame_index) = change_line_end_frame_index {
*change_line_end_frame_index
} else {
// So, for camera line, it is 1 off but not for the change line.
// If anything crashes, it is this one.
line_first_frame_idx - 1
};

// Enable mouse look so we can look around to have a good reference.
(callbacks.enable_mouse_look)();

// Change origin to the position of the end of that line.
let mut vieworg = self.branch().frames[end_frame_index].state.player.pos;

vieworg[2] += 28.;
if self.branch().frames[end_frame_index].state.player.ducking {
vieworg[2] -= 16.;
}

(callbacks.change_view_origin)(vieworg);

// Figuring whether the line is generally changing pitch or yaw.
let mode = match line {
Line::VectorialStrafingConstraints(_) => CameraViewAdjustmentMode::Yaw,
Line::Change(Change { target, .. }) => match target {
ChangeTarget::Yaw
| ChangeTarget::VectorialStrafingYaw
| ChangeTarget::VectorialStrafingYawOffset => {
CameraViewAdjustmentMode::Yaw
}
ChangeTarget::Pitch => CameraViewAdjustmentMode::Pitch,
},
_ => CameraViewAdjustmentMode::Alt,
};

self.camera_view_adjustment = Some(CameraViewAdjustment {
mode,
camera_line_idx: hovered_line_idx,
});
}
}
}

// Finally, update the previous mouse and keyboard state.
Expand Down Expand Up @@ -1832,6 +1945,130 @@ impl Editor {
Ok(())
}

pub fn tick_camera_view_adjustment(
&mut self,
mouse: MouseState,
prev_mouse: MouseState,
callbacks: Callbacks,
) -> eyre::Result<()> {
let Some(CameraViewAdjustment {
ref mut mode,
camera_line_idx,
}) = &mut self.camera_view_adjustment
else {
return Ok(());
};

// Switching line mode.
// Should be a click.
if mouse.buttons.is_left_down() && !prev_mouse.buttons.is_left_down() {
// Cycling
// Currently there is no `pitch` line...
*mode = match *mode {
CameraViewAdjustmentMode::Yaw => CameraViewAdjustmentMode::Pitch,
CameraViewAdjustmentMode::Pitch => CameraViewAdjustmentMode::Alt,
CameraViewAdjustmentMode::Alt => CameraViewAdjustmentMode::Yaw,
}
}

// Right mouse button hold means adjusting.
if mouse.buttons.is_right_down() {
// Keep looking around
return Ok(());
}

let branch = &mut self.branches[self.branch_idx];

let viewangles = (callbacks.get_viewangles)();
let line = &mut branch.branch.script.lines[*camera_line_idx];

// Disable as soon as possible in case of failure or lock.
(callbacks.disable_mouse_look)();

let mut buffer = Vec::new();
hltas::write::gen_line(&mut buffer, line)?;
let from = String::from_utf8(buffer)
.expect("Line serialization should never produce invalid UTF-8");

match line {
Line::VectorialStrafingConstraints(constraints) => match constraints {
// Since there is no `pitch` line,
// there won't be any attempts to change `target_yaw` into `pitch`.
// `target_yaw velocity_lock` will be changed to `target_yaw x`
VectorialStrafingConstraints::VelocityYawLocking { .. } => match mode {
CameraViewAdjustmentMode::Yaw => {
*constraints = VectorialStrafingConstraints::Yaw {
yaw: viewangles[1],
tolerance: 0.,
};
}
CameraViewAdjustmentMode::Pitch => (),
CameraViewAdjustmentMode::Alt => (),
},
VectorialStrafingConstraints::Yaw { yaw, .. } => {
match mode {
CameraViewAdjustmentMode::Yaw => *yaw = viewangles[1],
CameraViewAdjustmentMode::Pitch => (),
// Alt mode will change this to `target_yaw velocity_lock`.
CameraViewAdjustmentMode::Alt => {
*constraints =
VectorialStrafingConstraints::VelocityYawLocking { tolerance: 0. };
}
}
}
_ => (),
},
Line::Change(Change {
target,
final_value,
..
}) => {
match target {
ChangeTarget::Yaw | ChangeTarget::VectorialStrafingYaw => match mode {
CameraViewAdjustmentMode::Yaw => *final_value = viewangles[1],
CameraViewAdjustmentMode::Pitch => {
*target = ChangeTarget::Pitch;
*final_value = viewangles[0];
}
CameraViewAdjustmentMode::Alt => (),
},
ChangeTarget::Pitch => match mode {
CameraViewAdjustmentMode::Yaw => {
// Pitch will always do `target_yaw` change.
*target = ChangeTarget::VectorialStrafingYaw;
*final_value = viewangles[1];
}
CameraViewAdjustmentMode::Pitch => *final_value = viewangles[0],
CameraViewAdjustmentMode::Alt => (),
},
ChangeTarget::VectorialStrafingYawOffset => {
if matches!(mode, CameraViewAdjustmentMode::Yaw) {
*final_value = viewangles[1];
}
}
}
}
_ => (),
};

let mut buffer = Vec::new();
hltas::write::gen_line(&mut buffer, line)?;
let to = String::from_utf8(buffer)
.expect("Line serialization should never produce invalid UTF-8");

let op = Operation::Replace {
line_idx: *camera_line_idx,
from,
to,
};
self.apply_operation(op)?;

// Setting back to `None` to indicate finished operation.
self.camera_view_adjustment = None;

Ok(())
}

pub fn cancel_ongoing_adjustments(&mut self) {
if let Some(adjustment) = self.frame_count_adjustment.take() {
let original_value = adjustment.original_value;
Expand Down Expand Up @@ -2027,6 +2264,11 @@ impl Editor {

self.invalidate(curr_frame_idx);
}

// Camera adjustment only takes effects after the key is released.
// So there is no need to add this.
// if let Some(CameraViewAdjustment { mode, starting_frame_idx, camera_line_idx }) =
// self.camera_view_adjustment.take() {}
}

/// Stores already-applied operation.
Expand Down
Loading

0 comments on commit 42ca2bc

Please sign in to comment.