use viewport_lib::camera::camera::{Camera, CameraTarget};
use viewport_lib::interaction::input::ActionFrame;
use viewport_lib::interaction::selection::Selection;
use viewport_lib::runtime::{
CameraCommand, CameraFollowTarget, RuntimeFrameContext, RuntimeOutput, RuntimePlugin,
RuntimeStepContext, ViewportRuntime, apply_camera_commands, plugin::phase,
};
use viewport_lib::scene::scene::Scene;
fn make_frame(camera: &Camera, input: &ActionFrame) -> RuntimeFrameContext {
let mut frame = RuntimeFrameContext::default();
frame.dt = 1.0 / 60.0;
frame.camera = camera.clone();
frame.viewport_size = glam::Vec2::new(800.0, 600.0);
frame.input = input.clone();
frame
}
fn step(runtime: &mut ViewportRuntime, camera: &Camera) -> RuntimeOutput {
let input = ActionFrame::default();
let mut scene = Scene::new();
let mut sel = Selection::new();
runtime.step(&mut scene, &mut sel, &make_frame(camera, &input))
}
struct SetCenterPlugin(glam::Vec3);
impl RuntimePlugin for SetCenterPlugin {
fn priority(&self) -> i32 {
phase::ANIMATE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
ctx.output.events.emit(CameraCommand::SetCenter(self.0));
}
}
struct OffsetCenterPlugin(glam::Vec3);
impl RuntimePlugin for OffsetCenterPlugin {
fn priority(&self) -> i32 {
phase::POST_SIM
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
ctx.output.events.emit(CameraCommand::OffsetCenter(self.0));
}
}
struct SetDistancePlugin(f32);
impl RuntimePlugin for SetDistancePlugin {
fn priority(&self) -> i32 {
phase::ANIMATE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
ctx.output.events.emit(CameraCommand::SetDistance(self.0));
}
}
struct BlendPlugin {
target: CameraTarget,
weight: f32,
}
impl RuntimePlugin for BlendPlugin {
fn priority(&self) -> i32 {
phase::ANIMATE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
ctx.output.events.emit(CameraCommand::BlendToward {
target: self.target,
weight: self.weight,
});
}
}
#[test]
fn set_center_applies_to_camera() {
let mut runtime =
ViewportRuntime::new().with_plugin(SetCenterPlugin(glam::Vec3::new(1.0, 2.0, 3.0)));
let camera = Camera::default();
let mut output = step(&mut runtime, &camera);
let mut cam = camera.clone();
apply_camera_commands(&mut output.events, &mut cam);
assert!((cam.center - glam::Vec3::new(1.0, 2.0, 3.0)).length() < 1e-5);
}
#[test]
fn offset_center_adds_to_current_position() {
let mut camera = Camera::default();
camera.center = glam::Vec3::new(5.0, 0.0, 0.0);
let mut runtime =
ViewportRuntime::new().with_plugin(OffsetCenterPlugin(glam::Vec3::new(1.0, 0.0, 0.0)));
let mut output = step(&mut runtime, &camera);
apply_camera_commands(&mut output.events, &mut camera);
assert!((camera.center.x - 6.0).abs() < 1e-5);
}
#[test]
fn set_distance_applies() {
let mut runtime = ViewportRuntime::new().with_plugin(SetDistancePlugin(10.0));
let camera = Camera::default();
let mut output = step(&mut runtime, &camera);
let mut cam = camera.clone();
apply_camera_commands(&mut output.events, &mut cam);
assert!((cam.distance - 10.0).abs() < 1e-5);
}
#[test]
fn blend_toward_interpolates() {
let target = CameraTarget {
center: glam::Vec3::new(10.0, 0.0, 0.0),
distance: 20.0,
orientation: glam::Quat::IDENTITY,
};
let mut runtime = ViewportRuntime::new().with_plugin(BlendPlugin {
target,
weight: 0.5,
});
let mut camera = Camera::default();
camera.center = glam::Vec3::ZERO;
camera.distance = 4.0;
let mut output = step(&mut runtime, &camera);
apply_camera_commands(&mut output.events, &mut camera);
assert!((camera.center.x - 5.0).abs() < 1e-4);
assert!((camera.distance - 12.0).abs() < 1e-4);
}
#[test]
fn blend_at_weight_one_snaps_to_target() {
let target = CameraTarget {
center: glam::Vec3::new(7.0, 8.0, 9.0),
distance: 5.0,
orientation: glam::Quat::IDENTITY,
};
let mut runtime = ViewportRuntime::new().with_plugin(BlendPlugin {
target,
weight: 1.0,
});
let camera = Camera::default();
let mut output = step(&mut runtime, &camera);
let mut cam = camera.clone();
apply_camera_commands(&mut output.events, &mut cam);
assert!((cam.center - glam::Vec3::new(7.0, 8.0, 9.0)).length() < 1e-5);
assert!((cam.distance - 5.0).abs() < 1e-5);
}
#[test]
fn commands_applied_in_emission_order() {
let mut runtime = ViewportRuntime::new()
.with_plugin(SetCenterPlugin(glam::Vec3::new(10.0, 0.0, 0.0)))
.with_plugin(OffsetCenterPlugin(glam::Vec3::new(1.0, 0.0, 0.0)));
let camera = Camera::default();
let mut output = step(&mut runtime, &camera);
assert_eq!(output.events.read::<CameraCommand>().count(), 2);
let mut cam = camera.clone();
apply_camera_commands(&mut output.events, &mut cam);
assert!((cam.center.x - 11.0).abs() < 1e-5);
}
#[test]
fn no_commands_leaves_camera_unchanged() {
let mut runtime = ViewportRuntime::new();
let camera = Camera::default();
let original_center = camera.center;
let original_distance = camera.distance;
let mut output = step(&mut runtime, &camera);
let mut cam = camera.clone();
apply_camera_commands(&mut output.events, &mut cam);
assert_eq!(cam.center, original_center);
assert_eq!(cam.distance, original_distance);
}
#[test]
fn camera_commands_cleared_each_frame() {
let mut runtime = ViewportRuntime::new().with_plugin(SetCenterPlugin(glam::Vec3::ONE));
let camera = Camera::default();
step(&mut runtime, &camera);
let output2 = step(&mut runtime, &camera);
assert_eq!(output2.events.read::<CameraCommand>().count(), 1);
}
#[test]
fn camera_follow_target_independent_of_commands() {
let mut runtime = ViewportRuntime::new().with_plugin(SetCenterPlugin(glam::Vec3::ONE));
let camera = Camera::default();
let output = step(&mut runtime, &camera);
assert_eq!(output.events.read::<CameraFollowTarget>().count(), 0);
assert_eq!(output.events.read::<CameraCommand>().count(), 1);
}
#[test]
fn default_output_fields_empty() {
let mut runtime = ViewportRuntime::new().with_plugin(SetCenterPlugin(glam::Vec3::ZERO));
let camera = Camera::default();
let output = step(&mut runtime, &camera);
assert!(output.selection_ops.is_empty());
assert!(output.node_transform_ops.is_empty());
}
#[test]
fn set_orientation_applies() {
let q = glam::Quat::from_rotation_y(std::f32::consts::FRAC_PI_2);
struct SetOrientationPlugin(glam::Quat);
impl RuntimePlugin for SetOrientationPlugin {
fn priority(&self) -> i32 {
phase::ANIMATE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
ctx.output
.events
.emit(CameraCommand::SetOrientation(self.0));
}
}
let mut runtime = ViewportRuntime::new().with_plugin(SetOrientationPlugin(q));
let camera = Camera::default();
let mut output = step(&mut runtime, &camera);
let mut cam = camera.clone();
apply_camera_commands(&mut output.events, &mut cam);
assert!(cam.orientation.dot(q).abs() > 0.9999);
}