all_is_cubes_render/camera/
stdcam.rs1use alloc::sync::Arc;
2use core::fmt;
3
4use all_is_cubes::character::{Character, Cursor, cursor_raycast};
5use all_is_cubes::listen;
6use all_is_cubes::math::FreeCoordinate;
7use all_is_cubes::space::Space;
8use all_is_cubes::universe::{Handle, HandleError, ReadTicket, StrongHandle, Universe};
9
10use crate::camera::{Camera, GraphicsOptions, NdcPoint2, ViewTransform, Viewport};
11
12#[expect(clippy::exhaustive_structs)]
20#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
21pub struct Layers<T> {
22 pub world: T,
24 pub ui: T,
26}
27
28impl<T> Layers<T> {
29 pub fn splat(value: T) -> Self
31 where
32 T: Clone,
33 {
34 Layers {
35 world: value.clone(),
36 ui: value,
37 }
38 }
39
40 #[cfg(feature = "raytracer")]
42 pub(crate) fn as_refs(&self) -> Layers<&T> {
43 Layers {
44 world: &self.world,
45 ui: &self.ui,
46 }
47 }
48
49 #[cfg(feature = "raytracer")]
51 pub(crate) fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> Layers<U> {
52 Layers {
53 world: f(self.world),
54 ui: f(self.ui),
55 }
56 }
57
58 #[doc(hidden)] pub fn try_map_ref<U, E>(&self, mut f: impl FnMut(&T) -> Result<U, E>) -> Result<Layers<U>, E> {
60 Ok(Layers {
61 world: f(&self.world)?,
62 ui: f(&self.ui)?,
63 })
64 }
65}
66
67#[derive(Debug)]
90pub struct StandardCameras {
91 graphics_options: listen::DynSource<Arc<GraphicsOptions>>,
93 graphics_options_dirty: listen::Flag,
94
95 character_source: listen::DynSource<Option<StrongHandle<Character>>>,
96 character_dirty: listen::Flag,
98 character: Option<StrongHandle<Character>>,
99 world_space: listen::Cell<Option<Handle<Space>>>,
102
103 ui_source: listen::DynSource<Arc<UiViewState>>,
104 ui_dirty: listen::Flag,
105 ui_space: Option<Handle<Space>>,
106
107 viewport_source: listen::DynSource<Viewport>,
108 viewport_dirty: listen::Flag,
109
110 cameras: Layers<Camera>,
111}
112
113impl StandardCameras {
114 #[doc(hidden)]
120 pub fn new(
121 graphics_options: listen::DynSource<Arc<GraphicsOptions>>,
122 viewport_source: listen::DynSource<Viewport>,
123 character_source: listen::DynSource<Option<StrongHandle<Character>>>,
124 ui_source: listen::DynSource<Arc<UiViewState>>,
125 ) -> Self {
126 let graphics_options_dirty = listen::Flag::listening(false, &graphics_options);
130 let viewport_dirty = listen::Flag::listening(false, &viewport_source);
131
132 let initial_options: &GraphicsOptions = &graphics_options.get();
133 let initial_viewport: Viewport = viewport_source.get();
134
135 let ui_state = ui_source.get();
136
137 Self {
138 cameras: Layers {
139 ui: Camera::new((*ui_state.graphics_options).clone(), initial_viewport),
140 world: Camera::new(initial_options.clone(), initial_viewport),
141 },
142
143 graphics_options,
144 graphics_options_dirty,
145
146 character_dirty: listen::Flag::listening(true, &character_source),
147 character_source,
148 character: None, world_space: listen::Cell::new(None),
150
151 ui_space: ui_state.space.clone(),
152 ui_dirty: listen::Flag::listening(true, &ui_source),
153 ui_source,
154
155 viewport_dirty,
156 viewport_source,
157 }
158 }
159
160 #[doc(hidden)]
161 pub fn from_constant_for_test(
162 graphics_options: GraphicsOptions,
163 viewport: Viewport,
164 universe: &Universe,
165 ) -> Self {
166 let mut new_self = Self::new(
167 listen::constant(Arc::new(graphics_options)),
168 listen::constant(viewport),
169 listen::constant(universe.get_default_character().map(StrongHandle::new)),
170 listen::constant(Default::default()),
171 );
172 new_self.update(Layers {
173 world: universe.read_ticket(),
174 ui: ReadTicket::stub(),
175 });
176 new_self
177 }
178
179 pub fn update(&mut self, read_tickets: Layers<ReadTicket<'_>>) -> bool {
188 let mut anything_changed = false;
189
190 let options_dirty = self.graphics_options_dirty.get_and_clear();
191 if options_dirty {
192 anything_changed = true;
193 self.cameras.world.set_options((*self.graphics_options.get()).clone());
194 }
195
196 let ui_dirty = self.ui_dirty.get_and_clear();
197 if ui_dirty || options_dirty {
198 anything_changed = true;
199 let UiViewState {
200 space,
201 view_transform: ui_transform,
202 graphics_options: ui_options,
203 } = if self.graphics_options.get().show_ui {
204 (*self.ui_source.get()).clone()
205 } else {
206 UiViewState::default()
207 };
208 self.ui_space = space;
209 self.cameras.ui.set_options((*ui_options).clone());
210 self.cameras.ui.set_view_transform(ui_transform);
211 }
212
213 let viewport_dirty = self.viewport_dirty.get_and_clear();
217 if viewport_dirty {
218 anything_changed = true;
219 let viewport: Viewport = self.viewport_source.get();
220 self.cameras.world.set_viewport(viewport);
222 self.cameras.ui.set_viewport(viewport);
223 }
224
225 if self.character_dirty.get_and_clear() {
226 anything_changed = true;
227 self.character = self.character_source.get();
228 if self.character.is_none() {
229 self.cameras.world.set_view_transform(ViewTransform::identity());
232 }
233 }
234
235 if let Some(character_handle) = &self.character {
236 match Character::view(character_handle, read_tickets.world) {
237 Ok((space_handle, view_transform, exposure)) => {
238 if view_transform != self.cameras.world.view_transform() {
239 anything_changed = true;
240 self.cameras.world.set_view_transform(view_transform);
241 }
242
243 if Option::as_ref(&self.world_space.get()) != Some(space_handle) {
245 anything_changed = true;
246
247 self.world_space.set(Some(space_handle.clone()));
248 }
249
250 let old_actual_exposure = self.cameras.world.exposure();
252 self.cameras.world.set_measured_exposure(exposure);
253 anything_changed |= self.cameras.world.exposure() != old_actual_exposure;
254 }
255 Err(_) => {
256 }
258 }
259 } else {
260 if self.world_space.get().is_some() {
262 anything_changed = true;
263 self.world_space.set(None);
264 }
265 }
266
267 anything_changed
268 }
269
270 pub fn graphics_options(&self) -> &GraphicsOptions {
274 self.cameras.world.options()
275 }
276
277 pub fn graphics_options_source(&self) -> listen::DynSource<Arc<GraphicsOptions>> {
282 self.graphics_options.clone()
283 }
284
285 pub fn cameras(&self) -> &Layers<Camera> {
287 &self.cameras
288 }
289
290 pub fn character(&self) -> Option<&StrongHandle<Character>> {
293 self.character.as_ref()
294 }
295
296 pub fn world_space(&self) -> listen::DynSource<Option<Handle<Space>>> {
302 self.world_space.as_source()
303 }
304
305 pub fn ui_space(&self) -> Option<&Handle<Space>> {
312 self.ui_space.as_ref()
313 }
314
315 #[cfg(feature = "raytracer")] pub(crate) fn ui_view_source(&self) -> listen::DynSource<Arc<UiViewState>> {
320 self.ui_source.clone()
321 }
322
323 pub fn viewport(&self) -> Viewport {
328 self.cameras.world.viewport()
329 }
330
331 pub fn viewport_source(&self) -> listen::DynSource<Viewport> {
333 self.viewport_source.clone()
334 }
335
336 pub fn project_cursor(
341 &self,
342 read_tickets: Layers<ReadTicket<'_>>,
343 ndc_pos: NdcPoint2,
344 ) -> Result<Option<Cursor>, HandleError> {
345 if let Some(ui_space_handle) = self.ui_space.as_ref() {
346 let ray = self.cameras.ui.project_ndc_into_world(ndc_pos);
347 if let res @ (Ok(Some(_)) | Err(_)) = cursor_raycast(
348 read_tickets.ui,
349 ray,
350 ui_space_handle,
351 FreeCoordinate::INFINITY,
352 ) {
353 return res;
354 }
355 }
356
357 if let Some(character_handle) = self.character.as_ref() {
358 let ray = self.cameras.world.project_ndc_into_world(ndc_pos);
359 if let res @ (Ok(Some(_)) | Err(_)) = cursor_raycast(
362 read_tickets.world,
363 ray,
364 character_handle.read(read_tickets.world)?.space(),
365 6.0,
366 ) {
367 return res;
368 }
369 }
370
371 Ok(None)
372 }
373
374 #[must_use]
381 pub fn clone_unupdated(&self) -> Self {
382 Self::new(
383 self.graphics_options.clone(),
384 self.viewport_source.clone(),
385 self.character_source.clone(),
386 self.ui_source.clone(),
387 )
388 }
389}
390
391impl fmt::Pointer for StandardCameras {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 write!(
395 f,
396 indoc::indoc! {"\
397 StandardCameras {{
398 graphics_options: {graphics_options:p},
399 viewport_source: {viewport_source:p},
400 character_source: {character_source:p},
401 ui_source: {ui_source:p},
402 }}\
403 "},
404 graphics_options = self.graphics_options,
405 viewport_source = self.viewport_source,
406 character_source = self.character_source,
407 ui_source = self.ui_source,
408 )
409 }
410}
411
412#[derive(Clone, Debug, PartialEq)]
422#[expect(clippy::exhaustive_structs)]
423pub struct UiViewState {
424 pub space: Option<Handle<Space>>,
426
427 pub view_transform: ViewTransform,
429
430 pub graphics_options: Arc<GraphicsOptions>,
436}
437
438impl Default for UiViewState {
439 fn default() -> Self {
441 Self {
442 space: Default::default(),
443 view_transform: ViewTransform::identity(),
444 graphics_options: Default::default(),
445 }
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn cameras_follow_character_and_world() {
455 let character_cell = listen::Cell::new(None);
456 let mut cameras = StandardCameras::new(
457 listen::constant(Arc::new(GraphicsOptions::default())),
458 listen::constant(Viewport::ARBITRARY),
459 character_cell.as_source(),
460 listen::constant(Arc::new(UiViewState::default())),
461 );
462 cameras.update(Layers::splat(ReadTicket::stub()));
463
464 let world_source = cameras.world_space();
465 let world_flag = listen::Flag::listening(false, &world_source);
466 assert_eq!(world_source.get().as_ref(), None);
467
468 {
470 let changed = cameras.update(Layers::splat(ReadTicket::stub()));
471 assert_eq!((changed, world_flag.get_and_clear()), (false, false));
472 }
473
474 let mut universe = Universe::new();
476 let space_handle = universe.insert_anonymous(Space::empty_positive(1, 1, 1));
477 let character = universe
478 .insert(
479 "character".into(),
480 Character::spawn_default(universe.read_ticket(), space_handle.clone()).unwrap(),
481 )
482 .unwrap();
483 character_cell.set(Some(StrongHandle::new(character)));
484
485 {
487 assert!(!world_flag.get_and_clear());
488 let changed = cameras.update(Layers {
489 world: universe.read_ticket(),
490 ui: ReadTicket::stub(),
491 });
492 assert_eq!((changed, world_flag.get_and_clear()), (true, true));
493 assert_eq!(world_source.get().as_ref(), Some(&space_handle));
494 }
495
496 {
498 let changed = cameras.update(Layers {
499 world: universe.read_ticket(),
500 ui: ReadTicket::stub(),
501 });
502 assert_eq!((changed, world_flag.get_and_clear()), (false, false));
503 }
504
505 }
507
508 #[test]
509 fn cameras_clone() {
510 let options_cell = listen::Cell::new(Arc::new(GraphicsOptions::default()));
511 let mut cameras = StandardCameras::new(
512 options_cell.as_source(),
513 listen::constant(Viewport::ARBITRARY),
514 listen::constant(None),
515 listen::constant(Arc::new(UiViewState::default())),
516 );
517 cameras.update(Layers::splat(ReadTicket::stub()));
518 let mut cameras2 = cameras.clone_unupdated();
519 cameras2.update(Layers::splat(ReadTicket::stub()));
520
521 let default_o = GraphicsOptions::default();
522 let mut different_o = default_o.clone();
523 different_o.debug_chunk_boxes = true;
524 options_cell.set(Arc::new(different_o.clone()));
525
526 assert_eq!(cameras.cameras().world.options(), &default_o);
528 assert_eq!(cameras2.cameras().world.options(), &default_o);
529 cameras.update(Layers::splat(ReadTicket::stub()));
530 assert_eq!(cameras.cameras().world.options(), &different_o);
531 assert_eq!(cameras2.cameras().world.options(), &default_o);
532 cameras2.update(Layers::splat(ReadTicket::stub()));
533 assert_eq!(cameras.cameras().world.options(), &different_o);
534 assert_eq!(cameras2.cameras().world.options(), &different_o);
535 }
536}