use cgmath::{One, Point2};
use crate::camera::{Camera, GraphicsOptions, ViewTransform, Viewport};
use crate::character::{cursor_raycast, Character, Cursor};
use crate::listen::{DirtyFlag, ListenableCell, ListenableSource};
use crate::math::FreeCoordinate;
use crate::space::Space;
use crate::universe::{URef, Universe};
#[allow(clippy::exhaustive_structs)]
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Layers<T> {
pub world: T,
pub ui: T,
}
impl<T> Layers<T> {
pub(crate) fn as_refs(&self) -> Layers<&T> {
Layers {
world: &self.world,
ui: &self.ui,
}
}
pub(crate) fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> Layers<U> {
Layers {
world: f(self.world),
ui: f(self.ui),
}
}
#[doc(hidden)] pub fn try_map_ref<U, E>(&self, mut f: impl FnMut(&T) -> Result<U, E>) -> Result<Layers<U>, E> {
Ok(Layers {
world: f(&self.world)?,
ui: f(&self.ui)?,
})
}
}
#[derive(Debug)]
pub struct StandardCameras {
graphics_options: ListenableSource<GraphicsOptions>,
graphics_options_dirty: DirtyFlag,
character_source: ListenableSource<Option<URef<Character>>>,
character_dirty: DirtyFlag,
character: Option<URef<Character>>,
world_space: ListenableCell<Option<URef<Space>>>,
ui_source: ListenableSource<UiViewState>,
ui_dirty: DirtyFlag,
ui_space: Option<URef<Space>>,
viewport_source: ListenableSource<Viewport>,
viewport_dirty: DirtyFlag,
cameras: Layers<Camera>,
}
impl StandardCameras {
#[doc(hidden)]
pub fn new(
graphics_options: ListenableSource<GraphicsOptions>,
viewport_source: ListenableSource<Viewport>,
character_source: ListenableSource<Option<URef<Character>>>,
ui_source: ListenableSource<UiViewState>,
) -> Self {
let graphics_options_dirty = DirtyFlag::listening(false, |l| graphics_options.listen(l));
let viewport_dirty = DirtyFlag::listening(false, |l| viewport_source.listen(l));
let initial_options: &GraphicsOptions = &graphics_options.get();
let initial_viewport: Viewport = *viewport_source.get();
let ui_state = ui_source.get();
let mut new_self = Self {
cameras: Layers {
ui: Camera::new(ui_state.graphics_options.clone(), initial_viewport),
world: Camera::new(initial_options.clone(), initial_viewport),
},
graphics_options,
graphics_options_dirty,
character_dirty: DirtyFlag::listening(true, |l| character_source.listen(l)),
character_source,
character: None, world_space: ListenableCell::new(None),
ui_space: ui_state.space.clone(),
ui_dirty: DirtyFlag::listening(true, |l| ui_source.listen(l)),
ui_source,
viewport_dirty,
viewport_source,
};
new_self.update();
new_self
}
#[doc(hidden)]
pub fn from_constant_for_test(
graphics_options: GraphicsOptions,
viewport: Viewport,
universe: &Universe,
) -> Self {
Self::new(
ListenableSource::constant(graphics_options),
ListenableSource::constant(viewport),
ListenableSource::constant(universe.get_default_character()),
ListenableSource::constant(UiViewState::default()),
)
}
pub fn update(&mut self) {
let options_dirty = self.graphics_options_dirty.get_and_clear();
if options_dirty {
self.cameras
.world
.set_options(self.graphics_options.snapshot());
}
let ui_dirty = self.ui_dirty.get_and_clear();
if ui_dirty || options_dirty {
let UiViewState {
space,
view_transform: ui_transform,
graphics_options: ui_options,
} = if self.graphics_options.get().show_ui {
self.ui_source.snapshot()
} else {
UiViewState::default()
};
self.ui_space = space;
self.cameras.ui.set_options(ui_options);
self.cameras.ui.set_view_transform(ui_transform);
}
let viewport_dirty = self.viewport_dirty.get_and_clear();
if viewport_dirty {
let viewport: Viewport = self.viewport_source.snapshot();
self.cameras.world.set_viewport(viewport);
self.cameras.ui.set_viewport(viewport);
}
if self.character_dirty.get_and_clear() {
self.character = self.character_source.snapshot();
if self.character.is_none() {
self.cameras.world.set_view_transform(One::one());
}
}
if let Some(character_ref) = &self.character {
match character_ref.read() {
Ok(character) => {
self.cameras.world.set_view_transform(character.view());
if Option::as_ref(&*self.world_space.get()) != Some(&character.space) {
self.world_space.set(Some(character.space.clone()));
}
self.cameras
.world
.set_measured_exposure(character.exposure());
}
Err(_) => {
}
}
} else {
if self.world_space.get().is_some() {
self.world_space.set(None);
}
}
}
pub fn graphics_options(&self) -> &GraphicsOptions {
self.cameras.world.options()
}
pub fn graphics_options_source(&self) -> ListenableSource<GraphicsOptions> {
self.graphics_options.clone()
}
pub fn cameras(&self) -> &Layers<Camera> {
&self.cameras
}
pub fn character(&self) -> Option<&URef<Character>> {
self.character.as_ref()
}
pub fn world_space(&self) -> ListenableSource<Option<URef<Space>>> {
self.world_space.as_source()
}
pub fn ui_space(&self) -> Option<&URef<Space>> {
self.ui_space.as_ref()
}
pub fn viewport(&self) -> Viewport {
self.cameras.world.viewport()
}
pub fn viewport_source(&self) -> ListenableSource<Viewport> {
self.viewport_source.clone()
}
pub fn project_cursor(&self, ndc_pos: Point2<FreeCoordinate>) -> Option<Cursor> {
if let Some(ui_space_ref) = self.ui_space.as_ref() {
let ray = self.cameras.ui.project_ndc_into_world(ndc_pos);
if let Some(cursor) = cursor_raycast(ray, ui_space_ref, FreeCoordinate::INFINITY) {
return Some(cursor);
}
}
if let Some(character_ref) = self.character.as_ref() {
let ray = self.cameras.world.project_ndc_into_world(ndc_pos);
if let Some(cursor) = cursor_raycast(ray, &character_ref.read().unwrap().space, 6.0) {
return Some(cursor);
}
}
None
}
}
impl Clone for StandardCameras {
fn clone(&self) -> Self {
Self::new(
self.graphics_options.clone(),
self.viewport_source.clone(),
self.character_source.clone(),
self.ui_source.clone(),
)
}
}
#[derive(Clone, Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct UiViewState {
pub space: Option<URef<Space>>,
pub view_transform: ViewTransform,
pub graphics_options: GraphicsOptions, }
impl Default for UiViewState {
fn default() -> Self {
Self {
space: Default::default(),
view_transform: ViewTransform::one(),
graphics_options: Default::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::space::Space;
use crate::universe::{Universe, UniverseIndex};
#[test]
fn cameras_follow_character_and_world() {
let character_cell = ListenableCell::new(None);
let mut cameras = StandardCameras::new(
ListenableSource::constant(GraphicsOptions::default()),
ListenableSource::constant(Viewport::ARBITRARY),
character_cell.as_source(),
ListenableSource::constant(UiViewState::default()),
);
let world_source = cameras.world_space();
let flag = DirtyFlag::listening(false, |l| world_source.listen(l));
assert_eq!(world_source.snapshot().as_ref(), None);
cameras.update();
assert!(!flag.get_and_clear());
let mut universe = Universe::new();
let space_ref = universe.insert_anonymous(Space::empty_positive(1, 1, 1));
let character = universe
.insert(
"character".into(),
Character::spawn_default(space_ref.clone()),
)
.unwrap();
character_cell.set(Some(character));
assert!(!flag.get_and_clear());
cameras.update();
assert!(flag.get_and_clear());
assert_eq!(world_source.snapshot().as_ref(), Some(&space_ref));
cameras.update();
assert!(!flag.get_and_clear());
}
#[test]
fn cameras_clone() {
let options_cell = ListenableCell::new(GraphicsOptions::default());
let mut cameras = StandardCameras::new(
options_cell.as_source(),
ListenableSource::constant(Viewport::ARBITRARY),
ListenableSource::constant(None),
ListenableSource::constant(UiViewState::default()),
);
let mut cameras2 = cameras.clone();
let default_o = GraphicsOptions::default();
let mut different_o = default_o.clone();
different_o.debug_chunk_boxes = true;
options_cell.set(different_o.clone());
assert_eq!(cameras.cameras().world.options(), &default_o);
assert_eq!(cameras2.cameras().world.options(), &default_o);
cameras.update();
assert_eq!(cameras.cameras().world.options(), &different_o);
assert_eq!(cameras2.cameras().world.options(), &default_o);
cameras2.update();
assert_eq!(cameras.cameras().world.options(), &different_o);
assert_eq!(cameras2.cameras().world.options(), &different_o);
}
}