Skip to main content

box3d_rust/recording/
writers.rs

1//! Additional RecBuffer / SnapReader writers and readers for recording wire types.
2//! Port of b3RecW_* / b3RecR_* def and id helpers from recording.c / recording_replay.c.
3//!
4//! SPDX-FileCopyrightText: 2026 Erin Catto
5//! SPDX-License-Identifier: MIT
6
7use crate::constants::MAX_SHAPE_CAST_POINTS;
8use crate::distance::ShapeProxy;
9use crate::geometry::MassData;
10use crate::id::{BodyId, JointId, ShapeId, WorldId};
11use crate::math_functions::min_int;
12use crate::recording::buffer::{RecBuffer, SnapReader};
13use crate::types::{
14    default_body_def, default_distance_joint_def, default_explosion_def, default_filter_joint_def,
15    default_joint_def, default_motor_joint_def, default_parallel_joint_def,
16    default_prismatic_joint_def, default_query_filter, default_revolute_joint_def,
17    default_shape_def, default_spherical_joint_def, default_weld_joint_def,
18    default_wheel_joint_def, BodyDef, BodyType, DistanceJointDef, ExplosionDef, FilterJointDef,
19    JointDef, MotionLocks, MotorJointDef, ParallelJointDef, PrismaticJointDef, QueryFilter,
20    RevoluteJointDef, ShapeDef, SphericalJointDef, WeldJointDef, WheelJointDef,
21};
22
23impl RecBuffer {
24    /// (b3RecW_STR)
25    pub fn append_str(&mut self, s: &str) {
26        let bytes = s.as_bytes();
27        let len = bytes.len().min(65534);
28        self.append_u16(len as u16);
29        if len > 0 {
30            self.append(&bytes[..len]);
31        }
32    }
33
34    /// Null string sentinel used when C passes NULL. (b3RecW_STR NULL path)
35    pub fn append_str_null(&mut self) {
36        self.append_u16(0xFFFF);
37    }
38
39    pub fn append_world_id(&mut self, v: WorldId) {
40        self.append_u32(v.store());
41    }
42
43    pub fn append_body_id(&mut self, v: BodyId) {
44        self.append_u64(v.store());
45    }
46
47    pub fn append_shape_id(&mut self, v: ShapeId) {
48        self.append_u64(v.store());
49    }
50
51    pub fn append_joint_id(&mut self, v: JointId) {
52        self.append_u64(v.store());
53    }
54
55    pub fn append_mass_data(&mut self, v: MassData) {
56        self.append_f32(v.mass);
57        self.append_vec3(v.center);
58        self.append_matrix3(v.inertia);
59    }
60
61    pub fn append_locks(&mut self, v: MotionLocks) {
62        self.append_bool(v.linear_x);
63        self.append_bool(v.linear_y);
64        self.append_bool(v.linear_z);
65        self.append_bool(v.angular_x);
66        self.append_bool(v.angular_y);
67        self.append_bool(v.angular_z);
68    }
69
70    pub fn append_query_filter(&mut self, v: &QueryFilter) {
71        self.append_u64(v.category_bits);
72        self.append_u64(v.mask_bits);
73    }
74
75    pub fn append_shape_proxy(&mut self, v: &ShapeProxy) {
76        let mut count = v.count;
77        if count < 0 {
78            count = 0;
79        }
80        if count > MAX_SHAPE_CAST_POINTS as i32 {
81            count = MAX_SHAPE_CAST_POINTS as i32;
82        }
83        self.append_i32(count);
84        for i in 0..count {
85            self.append_vec3(v.points[i as usize]);
86        }
87        self.append_f32(v.radius);
88    }
89
90    pub fn append_explosion_def(&mut self, v: ExplosionDef) {
91        self.append_u64(v.mask_bits);
92        self.append_pos(v.position);
93        self.append_f32(v.radius);
94        self.append_f32(v.falloff);
95        self.append_f32(v.impulse_per_area);
96    }
97
98    /// (b3RecW_BODYDEF)
99    pub fn append_body_def(&mut self, v: &BodyDef) {
100        self.append_i32(v.type_ as i32);
101        self.append_pos(v.position);
102        self.append_quat(v.rotation);
103        self.append_vec3(v.linear_velocity);
104        self.append_vec3(v.angular_velocity);
105        self.append_f32(v.linear_damping);
106        self.append_f32(v.angular_damping);
107        self.append_f32(v.gravity_scale);
108        self.append_f32(v.sleep_threshold);
109        self.append_str(&v.name);
110        self.append_u64(0); // userData not preserved
111        self.append_locks(v.motion_locks);
112        self.append_bool(v.enable_sleep);
113        self.append_bool(v.is_awake);
114        self.append_bool(v.is_bullet);
115        self.append_bool(v.is_enabled);
116        self.append_bool(v.allow_fast_rotation);
117        self.append_bool(v.enable_contact_recycling);
118    }
119
120    /// (b3RecW_SHAPEDEF)
121    pub fn append_shape_def(&mut self, v: &ShapeDef) {
122        self.append_str(&v.name);
123        self.append_u64(0); // userData not preserved
124        let mat_count = v.materials.len() as i32;
125        self.append_i32(mat_count);
126        for m in &v.materials {
127            self.append_material(*m);
128        }
129        self.append_material(v.base_material);
130        self.append_f32(v.density);
131        self.append_f32(v.explosion_scale);
132        self.append_filter(v.filter);
133        self.append_bool(v.enable_custom_filtering);
134        self.append_bool(v.is_sensor);
135        self.append_bool(v.enable_sensor_events);
136        self.append_bool(v.enable_contact_events);
137        self.append_bool(v.enable_hit_events);
138        self.append_bool(v.enable_pre_solve_events);
139        self.append_bool(v.invoke_contact_creation);
140        self.append_bool(v.update_body_mass);
141    }
142
143    fn append_joint_base(&mut self, base: &JointDef) {
144        self.append_u64(0); // userData
145        self.append_body_id(base.body_id_a);
146        self.append_body_id(base.body_id_b);
147        self.append_transform(base.local_frame_a);
148        self.append_transform(base.local_frame_b);
149        self.append_f32(base.force_threshold);
150        self.append_f32(base.torque_threshold);
151        self.append_f32(base.constraint_hertz);
152        self.append_f32(base.constraint_damping_ratio);
153        self.append_f32(base.draw_scale);
154        self.append_bool(base.collide_connected);
155    }
156
157    pub fn append_parallel_joint_def(&mut self, v: &ParallelJointDef) {
158        self.append_joint_base(&v.base);
159        self.append_f32(v.hertz);
160        self.append_f32(v.damping_ratio);
161        self.append_f32(v.max_torque);
162    }
163
164    pub fn append_distance_joint_def(&mut self, v: &DistanceJointDef) {
165        self.append_joint_base(&v.base);
166        self.append_f32(v.length);
167        self.append_bool(v.enable_spring);
168        self.append_f32(v.lower_spring_force);
169        self.append_f32(v.upper_spring_force);
170        self.append_f32(v.hertz);
171        self.append_f32(v.damping_ratio);
172        self.append_bool(v.enable_limit);
173        self.append_f32(v.min_length);
174        self.append_f32(v.max_length);
175        self.append_bool(v.enable_motor);
176        self.append_f32(v.max_motor_force);
177        self.append_f32(v.motor_speed);
178    }
179
180    pub fn append_filter_joint_def(&mut self, v: &FilterJointDef) {
181        self.append_joint_base(&v.base);
182    }
183
184    pub fn append_motor_joint_def(&mut self, v: &MotorJointDef) {
185        self.append_joint_base(&v.base);
186        self.append_vec3(v.linear_velocity);
187        self.append_f32(v.max_velocity_force);
188        self.append_vec3(v.angular_velocity);
189        self.append_f32(v.max_velocity_torque);
190        self.append_f32(v.linear_hertz);
191        self.append_f32(v.linear_damping_ratio);
192        self.append_f32(v.max_spring_force);
193        self.append_f32(v.angular_hertz);
194        self.append_f32(v.angular_damping_ratio);
195        self.append_f32(v.max_spring_torque);
196    }
197
198    pub fn append_prismatic_joint_def(&mut self, v: &PrismaticJointDef) {
199        self.append_joint_base(&v.base);
200        self.append_bool(v.enable_spring);
201        self.append_f32(v.hertz);
202        self.append_f32(v.damping_ratio);
203        self.append_f32(v.target_translation);
204        self.append_bool(v.enable_limit);
205        self.append_f32(v.lower_translation);
206        self.append_f32(v.upper_translation);
207        self.append_bool(v.enable_motor);
208        self.append_f32(v.max_motor_force);
209        self.append_f32(v.motor_speed);
210    }
211
212    pub fn append_revolute_joint_def(&mut self, v: &RevoluteJointDef) {
213        self.append_joint_base(&v.base);
214        self.append_f32(v.target_angle);
215        self.append_bool(v.enable_spring);
216        self.append_f32(v.hertz);
217        self.append_f32(v.damping_ratio);
218        self.append_bool(v.enable_limit);
219        self.append_f32(v.lower_angle);
220        self.append_f32(v.upper_angle);
221        self.append_bool(v.enable_motor);
222        self.append_f32(v.max_motor_torque);
223        self.append_f32(v.motor_speed);
224    }
225
226    pub fn append_spherical_joint_def(&mut self, v: &SphericalJointDef) {
227        self.append_joint_base(&v.base);
228        self.append_bool(v.enable_spring);
229        self.append_f32(v.hertz);
230        self.append_f32(v.damping_ratio);
231        self.append_quat(v.target_rotation);
232        self.append_bool(v.enable_cone_limit);
233        self.append_f32(v.cone_angle);
234        self.append_bool(v.enable_twist_limit);
235        self.append_f32(v.lower_twist_angle);
236        self.append_f32(v.upper_twist_angle);
237        self.append_bool(v.enable_motor);
238        self.append_f32(v.max_motor_torque);
239        self.append_vec3(v.motor_velocity);
240    }
241
242    pub fn append_weld_joint_def(&mut self, v: &WeldJointDef) {
243        self.append_joint_base(&v.base);
244        self.append_f32(v.linear_hertz);
245        self.append_f32(v.angular_hertz);
246        self.append_f32(v.linear_damping_ratio);
247        self.append_f32(v.angular_damping_ratio);
248    }
249
250    pub fn append_wheel_joint_def(&mut self, v: &WheelJointDef) {
251        self.append_joint_base(&v.base);
252        self.append_bool(v.enable_suspension_spring);
253        self.append_f32(v.suspension_hertz);
254        self.append_f32(v.suspension_damping_ratio);
255        self.append_bool(v.enable_suspension_limit);
256        self.append_f32(v.lower_suspension_limit);
257        self.append_f32(v.upper_suspension_limit);
258        self.append_bool(v.enable_spin_motor);
259        self.append_f32(v.max_spin_torque);
260        self.append_f32(v.spin_speed);
261        self.append_bool(v.enable_steering);
262        self.append_f32(v.steering_hertz);
263        self.append_f32(v.steering_damping_ratio);
264        self.append_f32(v.target_steering_angle);
265        self.append_f32(v.max_steering_torque);
266        self.append_bool(v.enable_steering_limit);
267        self.append_f32(v.lower_steering_limit);
268        self.append_f32(v.upper_steering_limit);
269    }
270}
271
272impl<'a> SnapReader<'a> {
273    pub fn u24(&mut self) -> u32 {
274        let b0 = self.u8() as u32;
275        let b1 = self.u8() as u32;
276        let b2 = self.u8() as u32;
277        b0 | (b1 << 8) | (b2 << 16)
278    }
279
280    /// (b3RecR_STR) — empty string for the 0xFFFF null sentinel.
281    pub fn str_owned(&mut self) -> String {
282        let len = self.u16();
283        if len == 0xFFFF {
284            return String::new();
285        }
286        let n = len as usize;
287        if let Some(bytes) = self.bytes(n) {
288            String::from_utf8_lossy(bytes).into_owned()
289        } else {
290            String::new()
291        }
292    }
293
294    pub fn body_str(&mut self) -> String {
295        self.str_owned()
296    }
297
298    pub fn shape_str(&mut self) -> String {
299        self.str_owned()
300    }
301
302    pub fn world_id(&mut self) -> WorldId {
303        WorldId::load(self.u32())
304    }
305
306    pub fn body_id(&mut self) -> BodyId {
307        BodyId::load(self.u64())
308    }
309
310    pub fn shape_id(&mut self) -> ShapeId {
311        ShapeId::load(self.u64())
312    }
313
314    pub fn joint_id(&mut self) -> JointId {
315        JointId::load(self.u64())
316    }
317
318    pub fn mass_data(&mut self) -> MassData {
319        MassData {
320            mass: self.f32(),
321            center: self.vec3(),
322            inertia: self.matrix3(),
323        }
324    }
325
326    pub fn locks(&mut self) -> MotionLocks {
327        MotionLocks {
328            linear_x: self.bool(),
329            linear_y: self.bool(),
330            linear_z: self.bool(),
331            angular_x: self.bool(),
332            angular_y: self.bool(),
333            angular_z: self.bool(),
334        }
335    }
336
337    pub fn query_filter(&mut self) -> QueryFilter {
338        let mut f = default_query_filter();
339        f.category_bits = self.u64();
340        f.mask_bits = self.u64();
341        f
342    }
343
344    pub fn shape_proxy(&mut self) -> ShapeProxy {
345        let mut proxy = ShapeProxy::default();
346        let mut count = self.i32();
347        if count < 0 {
348            count = 0;
349        }
350        count = min_int(count, MAX_SHAPE_CAST_POINTS as i32);
351        proxy.count = count;
352        for i in 0..count {
353            proxy.points[i as usize] = self.vec3();
354        }
355        proxy.radius = self.f32();
356        proxy
357    }
358
359    pub fn explosion_def(&mut self) -> ExplosionDef {
360        let mut d = default_explosion_def();
361        d.mask_bits = self.u64();
362        d.position = self.pos();
363        d.radius = self.f32();
364        d.falloff = self.f32();
365        d.impulse_per_area = self.f32();
366        d
367    }
368
369    pub fn body_def(&mut self) -> BodyDef {
370        let mut d = default_body_def();
371        d.type_ = match self.i32() {
372            1 => BodyType::Kinematic,
373            2 => BodyType::Dynamic,
374            _ => BodyType::Static,
375        };
376        d.position = self.pos();
377        d.rotation = self.quat();
378        d.linear_velocity = self.vec3();
379        d.angular_velocity = self.vec3();
380        d.linear_damping = self.f32();
381        d.angular_damping = self.f32();
382        d.gravity_scale = self.f32();
383        d.sleep_threshold = self.f32();
384        d.name = self.str_owned();
385        let _user = self.u64();
386        d.motion_locks = self.locks();
387        d.enable_sleep = self.bool();
388        d.is_awake = self.bool();
389        d.is_bullet = self.bool();
390        d.is_enabled = self.bool();
391        d.allow_fast_rotation = self.bool();
392        d.enable_contact_recycling = self.bool();
393        d
394    }
395
396    pub fn shape_def(&mut self) -> ShapeDef {
397        let mut d = default_shape_def();
398        d.name = self.str_owned();
399        let _user = self.u64();
400        let mat_count = self.i32();
401        d.materials.clear();
402        if mat_count > 0 && self.ok {
403            d.materials.reserve(mat_count as usize);
404            for _ in 0..mat_count {
405                d.materials.push(self.material());
406            }
407        }
408        d.base_material = self.material();
409        d.density = self.f32();
410        d.explosion_scale = self.f32();
411        d.filter = self.filter();
412        d.enable_custom_filtering = self.bool();
413        d.is_sensor = self.bool();
414        d.enable_sensor_events = self.bool();
415        d.enable_contact_events = self.bool();
416        d.enable_hit_events = self.bool();
417        d.enable_pre_solve_events = self.bool();
418        d.invoke_contact_creation = self.bool();
419        d.update_body_mass = self.bool();
420        d
421    }
422
423    fn joint_base(&mut self) -> JointDef {
424        let mut base = default_joint_def();
425        let _user = self.u64();
426        base.body_id_a = self.body_id();
427        base.body_id_b = self.body_id();
428        base.local_frame_a = self.transform();
429        base.local_frame_b = self.transform();
430        base.force_threshold = self.f32();
431        base.torque_threshold = self.f32();
432        base.constraint_hertz = self.f32();
433        base.constraint_damping_ratio = self.f32();
434        base.draw_scale = self.f32();
435        base.collide_connected = self.bool();
436        base
437    }
438
439    pub fn parallel_joint_def(&mut self) -> ParallelJointDef {
440        let mut d = default_parallel_joint_def();
441        d.base = self.joint_base();
442        d.hertz = self.f32();
443        d.damping_ratio = self.f32();
444        d.max_torque = self.f32();
445        d
446    }
447
448    pub fn distance_joint_def(&mut self) -> DistanceJointDef {
449        let mut d = default_distance_joint_def();
450        d.base = self.joint_base();
451        d.length = self.f32();
452        d.enable_spring = self.bool();
453        d.lower_spring_force = self.f32();
454        d.upper_spring_force = self.f32();
455        d.hertz = self.f32();
456        d.damping_ratio = self.f32();
457        d.enable_limit = self.bool();
458        d.min_length = self.f32();
459        d.max_length = self.f32();
460        d.enable_motor = self.bool();
461        d.max_motor_force = self.f32();
462        d.motor_speed = self.f32();
463        d
464    }
465
466    pub fn filter_joint_def(&mut self) -> FilterJointDef {
467        let mut d = default_filter_joint_def();
468        d.base = self.joint_base();
469        d
470    }
471
472    pub fn motor_joint_def(&mut self) -> MotorJointDef {
473        let mut d = default_motor_joint_def();
474        d.base = self.joint_base();
475        d.linear_velocity = self.vec3();
476        d.max_velocity_force = self.f32();
477        d.angular_velocity = self.vec3();
478        d.max_velocity_torque = self.f32();
479        d.linear_hertz = self.f32();
480        d.linear_damping_ratio = self.f32();
481        d.max_spring_force = self.f32();
482        d.angular_hertz = self.f32();
483        d.angular_damping_ratio = self.f32();
484        d.max_spring_torque = self.f32();
485        d
486    }
487
488    pub fn prismatic_joint_def(&mut self) -> PrismaticJointDef {
489        let mut d = default_prismatic_joint_def();
490        d.base = self.joint_base();
491        d.enable_spring = self.bool();
492        d.hertz = self.f32();
493        d.damping_ratio = self.f32();
494        d.target_translation = self.f32();
495        d.enable_limit = self.bool();
496        d.lower_translation = self.f32();
497        d.upper_translation = self.f32();
498        d.enable_motor = self.bool();
499        d.max_motor_force = self.f32();
500        d.motor_speed = self.f32();
501        d
502    }
503
504    pub fn revolute_joint_def(&mut self) -> RevoluteJointDef {
505        let mut d = default_revolute_joint_def();
506        d.base = self.joint_base();
507        d.target_angle = self.f32();
508        d.enable_spring = self.bool();
509        d.hertz = self.f32();
510        d.damping_ratio = self.f32();
511        d.enable_limit = self.bool();
512        d.lower_angle = self.f32();
513        d.upper_angle = self.f32();
514        d.enable_motor = self.bool();
515        d.max_motor_torque = self.f32();
516        d.motor_speed = self.f32();
517        d
518    }
519
520    pub fn spherical_joint_def(&mut self) -> SphericalJointDef {
521        let mut d = default_spherical_joint_def();
522        d.base = self.joint_base();
523        d.enable_spring = self.bool();
524        d.hertz = self.f32();
525        d.damping_ratio = self.f32();
526        d.target_rotation = self.quat();
527        d.enable_cone_limit = self.bool();
528        d.cone_angle = self.f32();
529        d.enable_twist_limit = self.bool();
530        d.lower_twist_angle = self.f32();
531        d.upper_twist_angle = self.f32();
532        d.enable_motor = self.bool();
533        d.max_motor_torque = self.f32();
534        d.motor_velocity = self.vec3();
535        d
536    }
537
538    pub fn weld_joint_def(&mut self) -> WeldJointDef {
539        let mut d = default_weld_joint_def();
540        d.base = self.joint_base();
541        d.linear_hertz = self.f32();
542        d.angular_hertz = self.f32();
543        d.linear_damping_ratio = self.f32();
544        d.angular_damping_ratio = self.f32();
545        d
546    }
547
548    pub fn wheel_joint_def(&mut self) -> WheelJointDef {
549        let mut d = default_wheel_joint_def();
550        d.base = self.joint_base();
551        d.enable_suspension_spring = self.bool();
552        d.suspension_hertz = self.f32();
553        d.suspension_damping_ratio = self.f32();
554        d.enable_suspension_limit = self.bool();
555        d.lower_suspension_limit = self.f32();
556        d.upper_suspension_limit = self.f32();
557        d.enable_spin_motor = self.bool();
558        d.max_spin_torque = self.f32();
559        d.spin_speed = self.f32();
560        d.enable_steering = self.bool();
561        d.steering_hertz = self.f32();
562        d.steering_damping_ratio = self.f32();
563        d.target_steering_angle = self.f32();
564        d.max_steering_torque = self.f32();
565        d.enable_steering_limit = self.bool();
566        d.lower_steering_limit = self.f32();
567        d.upper_steering_limit = self.f32();
568        d
569    }
570}