Skip to main content

plasma_prp/camera/
brain.rs

1//! Camera brains — third-person follow, fixed, first-person, circle, drive.
2//!
3//! C++ ref: pfCamera/plCameraBrain.h/.cpp
4//!
5//! Inheritance hierarchy:
6//!   plCameraBrain1 (0x0099) — base
7//!   ├── plCameraBrain1_Avatar (0x009E) — third-person follow
8//!   │   └── plCameraBrain1_FirstPerson (0x00B3) — head cam
9//!   ├── plCameraBrain1_Fixed (0x009F) — locked to position
10//!   │   └── plCameraBrain1_Circle (0x00C2) — orbits a point
11//!   └── plCameraBrain1_Drive (0x009C) — WASD free-fly (built-in, no PRP data)
12
13use std::io::Read;
14
15use anyhow::Result;
16
17use crate::core::uoid::{Uoid, read_key_uoid};
18use crate::resource::prp::PlasmaRead;
19
20// ─── Brain flags (plCameraBrain.h:60-88) ───────────────────────────────────
21
22#[allow(dead_code)]
23pub mod brain_flags {
24    pub const CUT_POS: u32                  = 1 << 0;
25    pub const CUT_POS_ONCE: u32             = 1 << 1;
26    pub const CUT_POA: u32                  = 1 << 2;
27    pub const CUT_POA_ONCE: u32             = 1 << 3;
28    pub const ANIMATE_FOV: u32              = 1 << 4;
29    pub const FOLLOW_LOCAL_AVATAR: u32      = 1 << 5;
30    pub const PANIC_VELOCITY: u32           = 1 << 6;
31    pub const RAIL_COMPONENT: u32           = 1 << 7;
32    pub const SUBJECT: u32                  = 1 << 8;
33    pub const CIRCLE_TARGET: u32            = 1 << 9;
34    pub const MAINTAIN_LOS: u32             = 1 << 10;
35    pub const ZOOM_ENABLED: u32             = 1 << 11;
36    pub const IS_TRANSITION_CAMERA: u32     = 1 << 12;
37    pub const WORLDSPACE_POA: u32           = 1 << 13;
38    pub const WORLDSPACE_POS: u32           = 1 << 14;
39    pub const CUT_POS_WHILE_PAN: u32        = 1 << 15;
40    pub const CUT_POA_WHILE_PAN: u32        = 1 << 16;
41    pub const NON_PHYS: u32                 = 1 << 17;
42    pub const NEVER_ANIMATE_FOV: u32        = 1 << 18;
43    pub const IGNORE_SUBWORLD_MOVEMENT: u32 = 1 << 19;
44    pub const FALLING: u32                  = 1 << 20;
45    pub const RUNNING: u32                  = 1 << 21;
46    pub const VERTICAL_WHEN_FALLING: u32    = 1 << 22;
47    pub const SPEED_UP_WHEN_RUNNING: u32    = 1 << 23;
48    pub const FALLING_STOPPED: u32          = 1 << 24;
49    pub const BEGIN_FALLING: u32            = 1 << 25;
50}
51
52// ─── Circle camera flags (plCameraBrain.h:354-363) ─────────────────────────
53
54#[allow(dead_code)]
55pub mod circle_flags {
56    pub const LAGGED: u32              = 0x01;
57    pub const ABSOLUTE_LAG: u32        = 0x03; // kAbsoluteLag = kLagged | 0x02
58    pub const FARTHEST: u32            = 0x04;
59    pub const TARGETTED: u32           = 0x08;
60    pub const HAS_CENTER_OBJECT: u32   = 0x10;
61    pub const POA_OBJECT: u32          = 0x20;
62    pub const CIRCLE_LOCAL_AVATAR: u32 = 0x40;
63}
64
65// ─── Camera acceleration ───────────────────────────────────────────────────
66
67/// Camera brain acceleration parameters.
68#[derive(Debug, Clone, Copy)]
69pub struct CameraAccel {
70    pub accel: f32,
71    pub decel: f32,
72    pub max_vel: f32,
73}
74
75impl Default for CameraAccel {
76    fn default() -> Self {
77        // Plasma defaults from plCameraBrain1 constructor (plCameraBrain.cpp:98-114)
78        Self {
79            accel: 30.0,
80            decel: 30.0,
81            max_vel: 30.0,
82        }
83    }
84}
85
86// ─── Base brain fields ─────────────────────────────────────────────────────
87
88/// Shared fields from plCameraBrain1::Read (plCameraBrain.cpp:482-513).
89#[derive(Debug, Clone)]
90pub struct BrainBase {
91    pub self_key: Option<Uoid>,
92    pub poa_offset: [f32; 3],
93    pub subject_key: Option<Uoid>,
94    pub rail_key: Option<Uoid>,
95    pub flags: u32,
96    pub accel: CameraAccel,
97    pub poa_accel: CameraAccel,
98    pub x_pan_limit: f32,
99    pub z_pan_limit: f32,
100    pub zoom_rate: f32,
101    pub zoom_min: f32,
102    pub zoom_max: f32,
103}
104
105impl BrainBase {
106    pub fn has_flag(&self, flag: u32) -> bool {
107        self.flags & flag != 0
108    }
109
110    /// Read plCameraBrain1 base fields.
111    /// C++ ref: plCameraBrain.cpp:482-513
112    pub fn read(reader: &mut impl Read) -> Result<Self> {
113        let self_key = read_key_uoid(reader)?;
114
115        let poa_x = reader.read_f32()?;
116        let poa_y = reader.read_f32()?;
117        let poa_z = reader.read_f32()?;
118
119        let subject_key = read_key_uoid(reader)?;
120        let rail_key = read_key_uoid(reader)?;
121
122        // fFlags — hsBitVector: u32 count, then count × u32
123        let num_words = reader.read_u32()?;
124        let mut flags: u32 = 0;
125        for i in 0..num_words {
126            let bits = reader.read_u32()?;
127            if i == 0 {
128                flags = bits;
129            }
130        }
131
132        let accel_val = reader.read_f32()?;
133        let decel_val = reader.read_f32()?;
134        let velocity = reader.read_f32()?;
135        let poa_accel_val = reader.read_f32()?;
136        let poa_decel_val = reader.read_f32()?;
137        let poa_velocity = reader.read_f32()?;
138
139        let x_pan_limit = reader.read_f32()?;
140        let z_pan_limit = reader.read_f32()?;
141
142        let zoom_rate = reader.read_f32()?;
143        let zoom_min = reader.read_f32()?;
144        let zoom_max = reader.read_f32()?;
145
146        Ok(Self {
147            self_key,
148            poa_offset: [poa_x, poa_y, poa_z],
149            subject_key,
150            rail_key,
151            flags,
152            accel: CameraAccel { accel: accel_val, decel: decel_val, max_vel: velocity },
153            poa_accel: CameraAccel { accel: poa_accel_val, decel: poa_decel_val, max_vel: poa_velocity },
154            x_pan_limit,
155            z_pan_limit,
156            zoom_rate,
157            zoom_min,
158            zoom_max,
159        })
160    }
161}
162
163// ─── Avatar brain (third-person follow) ────────────────────────────────────
164
165/// plCameraBrain1_Avatar (0x009E).
166/// C++ ref: plCameraBrain.cpp:1251-1254
167#[derive(Debug, Clone)]
168pub struct AvatarBrainParams {
169    pub base: BrainBase,
170    pub offset: [f32; 3],
171}
172
173impl AvatarBrainParams {
174    /// Read plCameraBrain1_Avatar from PRP stream.
175    /// Format: BrainBase + fOffset(3f)
176    pub fn read(reader: &mut impl Read) -> Result<Self> {
177        let base = BrainBase::read(reader)?;
178        let off_x = reader.read_f32()?;
179        let off_y = reader.read_f32()?;
180        let off_z = reader.read_f32()?;
181
182        Ok(Self {
183            base,
184            offset: [off_x, off_y, off_z],
185        })
186    }
187}
188
189// ─── First-person brain ────────────────────────────────────────────────────
190
191/// plCameraBrain1_FirstPerson (0x00B3).
192/// Same binary format as Avatar — different runtime behavior only.
193#[derive(Debug, Clone)]
194pub struct FirstPersonBrainParams {
195    pub base: BrainBase,
196    pub offset: [f32; 3],
197}
198
199impl FirstPersonBrainParams {
200    pub fn read(reader: &mut impl Read) -> Result<Self> {
201        let base = BrainBase::read(reader)?;
202        let off_x = reader.read_f32()?;
203        let off_y = reader.read_f32()?;
204        let off_z = reader.read_f32()?;
205
206        Ok(Self {
207            base,
208            offset: [off_x, off_y, off_z],
209        })
210    }
211}
212
213// ─── Fixed brain ───────────────────────────────────────────────────────────
214
215/// plCameraBrain1_Fixed (0x009F).
216/// C++ ref: plCameraBrain.cpp:1455-1459
217#[derive(Debug, Clone)]
218pub struct FixedBrainParams {
219    pub base: BrainBase,
220    pub target_point: Option<Uoid>,
221}
222
223impl FixedBrainParams {
224    /// Read plCameraBrain1_Fixed: BrainBase + target_point_key
225    pub fn read(reader: &mut impl Read) -> Result<Self> {
226        let base = BrainBase::read(reader)?;
227        let target_point = read_key_uoid(reader)?;
228
229        Ok(Self {
230            base,
231            target_point,
232        })
233    }
234}
235
236// ─── Circle brain ──────────────────────────────────────────────────────────
237
238/// plCameraBrain1_Circle (0x00C2).
239/// C++ ref: plCameraBrain.cpp:1721-1741
240#[derive(Debug, Clone)]
241pub struct CircleBrainParams {
242    pub base: BrainBase,
243    pub circle_flags: u32,
244    pub center: [f32; 3],
245    pub radius: f32,
246    pub center_object: Option<Uoid>,
247    pub poa_object: Option<Uoid>,
248    pub cir_per_sec: f32,
249}
250
251impl CircleBrainParams {
252    /// Read plCameraBrain1_Circle from PRP stream.
253    /// C++ Read chain: plCameraBrain1::Read (NOT Fixed::Read) then circle-specific fields.
254    pub fn read(reader: &mut impl Read) -> Result<Self> {
255        let base = BrainBase::read(reader)?;
256
257        let circle_flags = reader.read_u32()?;
258
259        let cx = reader.read_f32()?;
260        let cy = reader.read_f32()?;
261        let cz = reader.read_f32()?;
262
263        let radius = reader.read_f32()?;
264
265        let center_object = read_key_uoid(reader)?;
266        let poa_object = read_key_uoid(reader)?;
267
268        let cir_per_sec = reader.read_f32()?;
269
270        Ok(Self {
271            base,
272            circle_flags,
273            center: [cx, cy, cz],
274            radius,
275            center_object,
276            poa_object,
277            cir_per_sec,
278        })
279    }
280}
281
282// ─── Unified brain enum ────────────────────────────────────────────────────
283
284/// All camera brain types parsed from PRP.
285#[derive(Debug, Clone)]
286pub enum CameraBrain {
287    Avatar(AvatarBrainParams),
288    FirstPerson(FirstPersonBrainParams),
289    Fixed(FixedBrainParams),
290    Circle(CircleBrainParams),
291    Drive,
292    Base(BrainBase),
293}
294
295impl CameraBrain {
296    pub fn base(&self) -> Option<&BrainBase> {
297        match self {
298            CameraBrain::Avatar(p) => Some(&p.base),
299            CameraBrain::FirstPerson(p) => Some(&p.base),
300            CameraBrain::Fixed(p) => Some(&p.base),
301            CameraBrain::Circle(p) => Some(&p.base),
302            CameraBrain::Base(b) => Some(b),
303            CameraBrain::Drive => None,
304        }
305    }
306
307    pub fn self_key(&self) -> Option<&Uoid> {
308        self.base().and_then(|b| b.self_key.as_ref())
309    }
310
311    /// Parse a camera brain from PRP stream given its class ID.
312    pub fn read_by_class(class_id: u16, reader: &mut impl Read) -> Result<Self> {
313        use crate::core::class_index::ClassIndex;
314        match class_id {
315            ClassIndex::PL_CAMERA_BRAIN1_AVATAR => {
316                Ok(CameraBrain::Avatar(AvatarBrainParams::read(reader)?))
317            }
318            ClassIndex::PL_CAMERA_BRAIN1_FIRST_PERSON => {
319                Ok(CameraBrain::FirstPerson(FirstPersonBrainParams::read(reader)?))
320            }
321            ClassIndex::PL_CAMERA_BRAIN1_FIXED => {
322                Ok(CameraBrain::Fixed(FixedBrainParams::read(reader)?))
323            }
324            ClassIndex::PL_CAMERA_BRAIN1_CIRCLE => {
325                Ok(CameraBrain::Circle(CircleBrainParams::read(reader)?))
326            }
327            ClassIndex::PL_CAMERA_BRAIN1 => {
328                Ok(CameraBrain::Base(BrainBase::read(reader)?))
329            }
330            _ => anyhow::bail!("Unknown camera brain class 0x{:04X}", class_id),
331        }
332    }
333
334    pub fn type_name(&self) -> &'static str {
335        match self {
336            CameraBrain::Avatar(_) => "Avatar",
337            CameraBrain::FirstPerson(_) => "FirstPerson",
338            CameraBrain::Fixed(_) => "Fixed",
339            CameraBrain::Circle(_) => "Circle",
340            CameraBrain::Drive => "Drive",
341            CameraBrain::Base(_) => "Base",
342        }
343    }
344}