Skip to main content

openworldformat/
avatar.rs

1//! Avatar definition — user/explorer presence in a world.
2
3use serde::{Deserialize, Serialize};
4
5use crate::identity::EntityRef;
6
7/// Camera point-of-view mode.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
9#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
10#[serde(rename_all = "snake_case")]
11pub enum PointOfView {
12    /// Camera at avatar eye level; avatar model not visible.
13    FirstPerson,
14    /// Camera orbits behind/above the avatar; avatar model visible.
15    #[default]
16    ThirdPerson,
17}
18
19/// Avatar configuration describing the user/explorer presence in a world.
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
22pub struct AvatarDef {
23    /// Where the avatar spawns in the world.
24    #[serde(default = "default_avatar_spawn")]
25    pub spawn_position: [f32; 3],
26    /// Initial look direction.
27    #[serde(default = "default_avatar_look_at")]
28    pub spawn_look_at: [f32; 3],
29    /// Camera point-of-view mode.
30    #[serde(default)]
31    pub pov: PointOfView,
32    /// Movement speed in units per second.
33    #[serde(default = "default_avatar_speed")]
34    pub movement_speed: f32,
35    /// Avatar eye-height above ground (used for first-person eye level).
36    #[serde(default = "default_avatar_height")]
37    pub height: f32,
38    /// Entity reference of the 3D model representing the avatar (3rd-person).
39    /// When `None`, the world has no visible avatar model.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    #[cfg_attr(feature = "schema", schemars(extend("x-entity-ref" = "bindable")))]
42    pub model_entity: Option<EntityRef>,
43}
44
45impl Default for AvatarDef {
46    fn default() -> Self {
47        Self {
48            spawn_position: default_avatar_spawn(),
49            spawn_look_at: default_avatar_look_at(),
50            pov: PointOfView::default(),
51            movement_speed: default_avatar_speed(),
52            height: default_avatar_height(),
53            model_entity: None,
54        }
55    }
56}
57
58fn default_avatar_spawn() -> [f32; 3] {
59    [0.0, 0.0, 5.0]
60}
61fn default_avatar_look_at() -> [f32; 3] {
62    [0.0, 0.0, 0.0]
63}
64fn default_avatar_speed() -> f32 {
65    5.0
66}
67fn default_avatar_height() -> f32 {
68    1.8
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn default_avatar() {
77        let a = AvatarDef::default();
78        assert_eq!(a.pov, PointOfView::ThirdPerson);
79        assert_eq!(a.movement_speed, 5.0);
80        assert_eq!(a.height, 1.8);
81        assert!(a.model_entity.is_none());
82    }
83
84    #[test]
85    fn avatar_roundtrip() {
86        let a = AvatarDef {
87            spawn_position: [1.0, 1.8, 5.0],
88            spawn_look_at: [0.0, 1.5, 0.0],
89            pov: PointOfView::FirstPerson,
90            movement_speed: 3.0,
91            height: 1.8,
92            model_entity: Some(EntityRef::name("player_model")),
93        };
94        let json = serde_json::to_string(&a).unwrap();
95        let back: AvatarDef = serde_json::from_str(&json).unwrap();
96        assert_eq!(a, back);
97    }
98}