Skip to main content

mittens_engine/engine/ecs/component/
avatar_control.rs

1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::component::Component;
3
4/// Coordinates all pose drivers for a humanoid avatar.
5///
6/// **Design rule**: every transform driver that moves this avatar's bones must be a
7/// child of (or otherwise routed through) this component.  This includes the primary
8/// body/head driver (`Input` / `InputXR`) and any hand controllers (`ControllerXR`).
9/// Uncoordinated drivers that bypass this component and write directly to armature bones
10/// are the root cause of the torso-rotation bug in the old two-input design.
11///
12/// Multiple drivers are fine; what matters is that they all appear in this node's
13/// subtree so `AvatarControlSystem` can discover and route them during init.
14///
15/// ## Controller discovery
16///
17/// Hand controllers are discovered automatically by topology: any `ControllerXRComponent`
18/// that is a **direct child** of this component is registered as a hand driver.
19/// Its `hand` field (`Left` / `Right`) determines which hand bone it drives.
20/// The bone is displaced under the controller's first `TransformComponent` child
21/// (the driven transform written by `OpenXRSystem`).
22///
23/// If no controller is present for a configured hand bone, a plain
24/// `TransformComponent` splice is inserted instead (for IK-only or static setups).
25///
26/// ## Topology (after init)
27///
28/// ```text
29/// Input  (or  InputXR)                    ← primary driver
30///   └── driven_t
31///         └── AvatarControlComponent
32///               ├── model_root  (TransformComponent, Y offset)
33///               │     └── GLTFComponent
34///               │           └── [armature]
35///               │                 J_Bip_C_Neck
36///               │                   └── splice_head  ← injected by system
37///               │                         └── J_Bip_C_Head (displaced; aim-driven by driven_t)
38///               │                 left_lower_arm
39///               │                   └── ControllerXR (Left, Grip)  ← moved here by system
40///               │                         └── controller_driven_t
41///               │                               └── J_Bip_L_Hand (displaced)
42///               │                 right_lower_arm
43///               │                   └── ControllerXR (Right, Grip)
44///               │                         └── controller_driven_t
45///               │                               └── J_Bip_R_Hand (displaced)
46///               ├── ControllerXR (Left,  Grip) { T }  ← declared here; re-parented on init
47///               └── ControllerXR (Right, Grip) { T }
48/// ```
49#[derive(Debug, Clone)]
50pub struct AvatarControlComponent {
51    /// Name of the bone to displace for head rotation. Default: "J_Bip_C_Head".
52    ///
53    /// This bone receives the HMD/Input world rotation directly via an `AimConstraint`
54    /// IK chain. Rotating the head bone (not the neck) is critical for VR/desktop:
55    /// rotating the neck twists the entire torso from the neck up, which looks wrong.
56    /// The head's rotation is isolated from the spine so the body can yaw-follow
57    /// underneath independently.
58    pub head_bone: String,
59
60    /// Name of the left hand bone to splice. `None` = no left hand splice.
61    pub left_hand_bone: Option<String>,
62
63    /// Name of the right hand bone to splice. `None` = no right hand splice.
64    pub right_hand_bone: Option<String>,
65
66    /// Explicit left upper arm bone name for TwoBoneIK.
67    /// If `None` and `left_hand_bone` is set, topology derivation fills it in.
68    pub left_upper_arm_bone: Option<String>,
69
70    /// Explicit left lower arm bone name for TwoBoneIK.
71    /// If `None` and `left_hand_bone` is set, topology derivation fills it in.
72    pub left_lower_arm_bone: Option<String>,
73
74    /// Explicit right upper arm bone name for TwoBoneIK.
75    /// If `None` and `right_hand_bone` is set, topology derivation fills it in.
76    pub right_upper_arm_bone: Option<String>,
77
78    /// Explicit right lower arm bone name for TwoBoneIK.
79    /// If `None` and `right_hand_bone` is set, topology derivation fills it in.
80    pub right_lower_arm_bone: Option<String>,
81
82    /// Body-local pole hint for the left elbow in the 2-bone arm IK solve.
83    /// Transformed to world-space each tick by the solver using the model root
84    /// rotation, so the elbow stays anatomically correct when the body turns.
85    /// Default `[-1, 0, -1]` (elbow out + slightly back).
86    pub left_arm_pole_direction: [f32; 3],
87
88    /// Body-local pole hint for the right elbow in the 2-bone arm IK solve.
89    /// Transformed to world-space each tick by the solver using the model root
90    /// rotation, so the elbow stays anatomically correct when the body turns.
91    /// Default `[1, 0, -1]`.
92    pub right_arm_pole_direction: [f32; 3],
93
94    /// Yaw delta (radians) that triggers body rotation. Default: π/4 (45°).
95    pub body_yaw_threshold: f32,
96
97    /// Body rotation rate (radians/sec). Default: 3.0.
98    pub body_yaw_rate: f32,
99
100    /// Use +Z as the authored forward axis override.
101    ///
102    /// When not explicitly overridden, AVC keeps the shared XR-style default
103    /// (`false`) for both desktop and XR. This override remains available for
104    /// assets that were authored with a different convention.
105    pub forward_plus_z: bool,
106
107    /// Whether `forward_plus_z` was explicitly authored as an override.
108    pub forward_plus_z_overridden: bool,
109
110    /// Initial body yaw (radians) seeded into the `YawFollow` pipeline op.
111    ///
112    /// When not explicitly overridden, AVC uses the shared default `π`.
113    pub initial_body_yaw: f32,
114
115    /// Whether `initial_body_yaw` was explicitly authored as an override.
116    pub initial_body_yaw_overridden: bool,
117
118    /// Optional rotation smoothing for hand pose drivers (ControllerXR etc.).
119    /// Applied to the rotation channel of each discovered hand driver's pipeline.
120    /// Equivalent to `QuatTemporalFilter` smoothing_factor. `None` = no smoothing pipeline.
121    pub hand_rotation_smoothing: Option<f32>,
122
123    /// Bone used as the camera anchor and as the source for auto-calibrating model_root.y.
124    ///
125    /// When set, `AvatarControlSystem` will:
126    ///   1. Measure this bone's local Y height above model_root in the GLTF rest pose.
127    ///   2. Override model_root's Y translation to `-bone_local_y`, so the bone sits
128    ///      exactly at `driven_t`'s world position (= HMD height in XR; body origin on desktop).
129    ///   3. Re-parent any `Camera3DComponent` or `CameraXRComponent` direct children of
130    ///      this AVC under this bone, giving them the bone's world transform each tick.
131    ///
132    /// Typically the same as `head_bone` (e.g. `"J_Bip_C_Head"`) so the camera
133    /// inherits both the head's world position (eye height) and rotation.
134    /// If `None`, no auto-calibration or camera re-parenting is performed.
135    pub camera_bone: Option<String>,
136
137    /// Explicit avatar height (metres) used to set model_root.y = -avatar_height.
138    /// Overrides the camera_bone auto-calibration if both are set.
139    /// Use this when the camera bone lookup fails or the mesh height is known in advance.
140    pub avatar_height: Option<f32>,
141
142    /// Name of the hips bone — the FABRIK spine chain root.  Default: `"J_Bip_C_Hips"`.
143    ///
144    /// Resolved against `model_root` once during `try_init_splices`.  If `None` or
145    /// not found, no spine FABRIK chain is wired — head bone falls back to FK
146    /// (visible detachment from neck under pitch).
147    pub hips_bone: Option<String>,
148
149    /// Vertical distance (metres) from the head bone pivot to the eyes.
150    ///
151    /// VRM `J_Bip_C_Head` pivot sits at the skull base; the eye line is typically
152    /// ~0.08 m above that.  When this is set, AVC shifts `model_root.y` down by
153    /// this amount so the EYES (not the bone pivot) land at `driven_t`'s world Y
154    /// — i.e. at HMD height in VR, or at the desktop input height.
155    ///
156    /// Without this, the avatar's eyes sit above the HMD eye position and the
157    /// face/hair mesh swings into the XR camera frustum when pitching down.
158    ///
159    /// Applies on top of either `camera_bone` auto-calibration or
160    /// `avatar_height` override.  Default: `None` (no adjustment).
161    pub eye_height_from_head_bone: Option<f32>,
162
163    /// Vertical offset (metres) used exclusively for the head IK target calculation.
164    ///
165    /// This is decoupled from the camera position transform (`T { CXR }` wrapper)
166    /// so the camera can be positioned freely without affecting how the FABRIK solver
167    /// bends the spine.  Typically set to a small value like 0.04–0.08 to account for
168    /// the gap between the head bone pivot and the eye position, causing the spine to
169    /// bend so the head lands at the right height relative to the HMD.
170    ///
171    /// When set, the FABRIK target_position_offset uses this value (Y-only) instead of
172    /// reading the camera transform's translation.  If `None`, no offset is applied to
173    /// the IK target (the head bone pivot chases the HMD position directly).
174    /// Default: `None`.
175    pub head_ik_eye_height: Option<f32>,
176
177    /// Local-space rotation offset applied to the left hand IK target (after grip pose).
178    ///
179    /// Use this to correct for the gap between how the runtime reports grip orientation
180    /// and what the armature expects for the hand bone. For standard VRM rigs with
181    /// Vive-family controllers, `quat_rotation_y(PI/2)` (90° CW from above) is typical.
182    /// `None` = no offset (raw grip pose drives the hand bone directly).
183    pub hand_grip_rotation_left: Option<[f32; 4]>,
184
185    /// Local-space rotation offset applied to the right hand IK target (after grip pose).
186    ///
187    /// For standard VRM rigs with Vive-family controllers, `quat_rotation_y(-PI/2)`
188    /// (90° CCW from above) is typical.
189    /// `None` = no offset.
190    pub hand_grip_rotation_right: Option<[f32; 4]>,
191
192    /// Enable interactive capture of live hand grip offsets.
193    ///
194    /// When enabled, pressing `Enter` captures the current controller-to-hand
195    /// rotation offset for the first initialized AVC in the world and prints
196    /// MMS-ready `hand_grip_rotation_left/right([...])` lines to the console.
197    pub calibrate_hand_transforms: bool,
198
199    // Runtime IDs set by AvatarControlSystem on first tick:
200    pub(crate) splice_head: Option<ComponentId>,
201    pub(crate) displaced_head: Option<ComponentId>,
202    /// Cached left hand bone id (end effector of left-arm TwoBoneIK).
203    pub(crate) left_hand_bone_id: Option<ComponentId>,
204    /// Cached right hand bone id (end effector of right-arm TwoBoneIK).
205    pub(crate) right_hand_bone_id: Option<ComponentId>,
206    /// Raw left controller/grip transform that feeds the optional hand offset node.
207    pub(crate) left_hand_raw_target_id: Option<ComponentId>,
208    /// Raw right controller/grip transform that feeds the optional hand offset node.
209    pub(crate) right_hand_raw_target_id: Option<ComponentId>,
210    /// Final left visual hand target transform used by IK.
211    pub(crate) left_hand_visual_target_id: Option<ComponentId>,
212    /// Final right visual hand target transform used by IK.
213    pub(crate) right_hand_visual_target_id: Option<ComponentId>,
214
215    /// ComponentId of the body pipeline root (`TransformForkTRSComponent`).
216    /// Set by `try_init_splices`.
217    pub(crate) body_pipeline_id: Option<ComponentId>,
218
219    /// The bone component that cameras were re-parented under (= `camera_bone` lookup result).
220    /// Set by `try_init_splices` when `camera_bone` is `Some`.
221    pub(crate) splice_camera_bone: Option<ComponentId>,
222
223    /// Debug/diagnostic flag: skip creation of the body-rotation pipeline entirely.
224    /// When `true`, model_root stays directly under AVC and only head rotation is applied.
225    /// Use this to isolate whether torso-twist bugs originate in the body pipeline.
226    pub skip_body_pipeline: bool,
227
228    /// Debug/diagnostic flag: when enabled, arm TwoBoneIK chains spawn overlay
229    /// visualizations for the actual target vector, transformed pole vector,
230    /// bend-plane normal, and solved elbow direction used by the solver.
231    pub ik_debug: bool,
232
233    // ---------------------------------------------------------------------
234    // Head-pose-sensitive body XZ translate follow (see
235    // `docs/task/avatar-control-simple-humanoid-body-follow.md`, Phase 1).
236    // ---------------------------------------------------------------------
237    /// Name of the neck bone used by the Phase 2 rest-pin.  When set and
238    /// the bone is found under `model_root`, the body-follow system records
239    /// its rest local translation at init and restores it each tick if any
240    /// other system perturbs it.  Default: `"J_Bip_C_Neck"`.
241    pub neck_bone: Option<String>,
242
243    // Runtime state set by AvatarControlSystem / HeadPoseBodyXzFollowSystem:
244    /// `model_root` component id, stashed at init so the body-follow system
245    /// doesn't have to re-walk topology each tick.
246    pub(crate) model_root_id: Option<ComponentId>,
247
248    /// `model_root.local.translation.y` at rest (body height offset).  Set
249    /// once at init from `camera_bone` auto-calibration or `avatar_height`.
250    pub(crate) model_root_local_y: f32,
251
252    /// Resolved neck bone id (under `model_root`).  `None` if not found.
253    pub(crate) neck_bone_id: Option<ComponentId>,
254
255    /// Neck rest local translation cached at init for the rest-pin.
256    pub(crate) neck_rest_translation: Option<[f32; 3]>,
257
258    component: Option<ComponentId>,
259}
260
261impl AvatarControlComponent {
262    pub fn new() -> Self {
263        Self::default()
264    }
265
266    pub fn with_head_bone(mut self, name: impl Into<String>) -> Self {
267        self.head_bone = name.into();
268        self
269    }
270
271    pub fn with_left_hand_bone(mut self, name: impl Into<String>) -> Self {
272        self.left_hand_bone = Some(name.into());
273        self
274    }
275
276    pub fn with_right_hand_bone(mut self, name: impl Into<String>) -> Self {
277        self.right_hand_bone = Some(name.into());
278        self
279    }
280
281    pub fn with_left_upper_arm_bone(mut self, name: impl Into<String>) -> Self {
282        self.left_upper_arm_bone = Some(name.into());
283        self
284    }
285
286    pub fn with_left_lower_arm_bone(mut self, name: impl Into<String>) -> Self {
287        self.left_lower_arm_bone = Some(name.into());
288        self
289    }
290
291    pub fn with_right_upper_arm_bone(mut self, name: impl Into<String>) -> Self {
292        self.right_upper_arm_bone = Some(name.into());
293        self
294    }
295
296    pub fn with_right_lower_arm_bone(mut self, name: impl Into<String>) -> Self {
297        self.right_lower_arm_bone = Some(name.into());
298        self
299    }
300
301    /// Override the left elbow pole direction (body-local).
302    pub fn with_left_arm_pole_direction(mut self, dir: [f32; 3]) -> Self {
303        self.left_arm_pole_direction = dir;
304        self
305    }
306
307    /// Override the right elbow pole direction (body-local).
308    pub fn with_right_arm_pole_direction(mut self, dir: [f32; 3]) -> Self {
309        self.right_arm_pole_direction = dir;
310        self
311    }
312
313    pub fn with_body_yaw_threshold(mut self, t: f32) -> Self {
314        self.body_yaw_threshold = t;
315        self
316    }
317
318    pub fn with_body_yaw_rate(mut self, r: f32) -> Self {
319        self.body_yaw_rate = r;
320        self
321    }
322
323    /// Override the initial body yaw (radians) seeded into the `YawFollow` pipeline op.
324    /// Use `std::f32::consts::PI` for rigs that face -Z at rest.
325    pub fn with_initial_yaw(mut self, yaw: f32) -> Self {
326        self.initial_body_yaw = yaw;
327        self.initial_body_yaw_overridden = true;
328        self
329    }
330
331    /// Use +Z as the authored forward axis override.
332    pub fn with_forward_plus_z(mut self) -> Self {
333        self.forward_plus_z = true;
334        self.forward_plus_z_overridden = true;
335        self
336    }
337
338    /// Enable rotation smoothing for hand pose drivers.
339    /// Set to e.g. `220.0` for smooth VR controller rotation.
340    pub fn with_hand_rotation_smoothing(mut self, factor: f32) -> Self {
341        self.hand_rotation_smoothing = Some(factor);
342        self
343    }
344
345    /// Skip creation of the body-rotation pipeline. Only head rotation will be applied.
346    /// Use to isolate whether torso-twist bugs originate in the body pipeline.
347    pub fn with_body_pipeline_disabled(mut self) -> Self {
348        self.skip_body_pipeline = true;
349        self
350    }
351
352    /// Enable TwoBoneIK debug visualizations for chains owned by this AVC.
353    pub fn with_ik_debug(mut self) -> Self {
354        self.ik_debug = true;
355        self
356    }
357
358    /// Set the bone used as the camera anchor and for auto-calibrating `model_root.y`.
359    ///
360    /// `AvatarControlSystem` will measure this bone's local Y in the rest pose and set
361    /// `model_root.y = -bone_local_y` so the bone sits at `driven_t`'s world position.
362    /// Any `Camera3DComponent` or `CameraXRComponent` direct children of this AVC are
363    /// re-parented under this bone during init.
364    pub fn with_camera_bone(mut self, name: impl Into<String>) -> Self {
365        self.camera_bone = Some(name.into());
366        self
367    }
368
369    /// Explicitly set `model_root.y = -height` during init, bypassing camera_bone
370    /// auto-calibration.  Use when the bone lookup is unreliable or the mesh height
371    /// is known in advance.  Camera re-parenting still uses `camera_bone` if set.
372    pub fn with_avatar_height(mut self, height: f32) -> Self {
373        self.avatar_height = Some(height);
374        self
375    }
376
377    /// Shift `model_root.y` down so the avatar's EYES (not the head bone pivot)
378    /// land at `driven_t`'s world Y.  Default eye offset for VRM is ~0.08.
379    pub fn with_eye_height_from_head_bone(mut self, dy: f32) -> Self {
380        self.eye_height_from_head_bone = Some(dy);
381        self
382    }
383
384    /// Set the hips bone name — root of the spine FABRIK chain.
385    /// Default (when unset): `"J_Bip_C_Hips"`.
386    pub fn with_hips_bone(mut self, name: impl Into<String>) -> Self {
387        self.hips_bone = Some(name.into());
388        self
389    }
390
391    /// Override the neck bone name used by the Phase 2 rest-pin.  Pass `None`
392    /// to disable the pin entirely.
393    pub fn with_neck_bone(mut self, name: impl Into<String>) -> Self {
394        self.neck_bone = Some(name.into());
395        self
396    }
397
398    /// Disable the neck rest-pin.
399    pub fn without_neck_pin(mut self) -> Self {
400        self.neck_bone = None;
401        self
402    }
403
404    /// Set the vertical offset for the head IK target calculation (metres).
405    /// Decoupled from the camera position so spine bending and camera positioning
406    /// can be controlled independently. Default: `None`.
407    pub fn with_head_ik_eye_height(mut self, dy: f32) -> Self {
408        self.head_ik_eye_height = Some(dy);
409        self
410    }
411
412    /// Set a rotation offset applied to the left hand grip IK target.
413    /// Quaternion in `[x, y, z, w]` order.
414    pub fn with_hand_grip_rotation_left(mut self, q: [f32; 4]) -> Self {
415        self.hand_grip_rotation_left = Some(q);
416        self
417    }
418
419    /// Set a rotation offset applied to the right hand grip IK target.
420    pub fn with_hand_grip_rotation_right(mut self, q: [f32; 4]) -> Self {
421        self.hand_grip_rotation_right = Some(q);
422        self
423    }
424
425    pub fn with_calibrate_hand_transforms(mut self) -> Self {
426        self.calibrate_hand_transforms = true;
427        self
428    }
429}
430
431impl Default for AvatarControlComponent {
432    fn default() -> Self {
433        Self {
434            head_bone: "J_Bip_C_Head".to_string(),
435            left_hand_bone: None,
436            right_hand_bone: None,
437            left_upper_arm_bone: None,
438            left_lower_arm_bone: None,
439            right_upper_arm_bone: None,
440            right_lower_arm_bone: None,
441            left_arm_pole_direction: [-1.0, 0.0, -1.0],
442            right_arm_pole_direction: [1.0, 0.0, -1.0],
443            body_yaw_threshold: std::f32::consts::FRAC_PI_4,
444            body_yaw_rate: 3.0,
445            forward_plus_z: false,
446            forward_plus_z_overridden: false,
447            initial_body_yaw: 0.0,
448            initial_body_yaw_overridden: false,
449            hand_rotation_smoothing: None,
450            camera_bone: None,
451            avatar_height: None,
452            hips_bone: None,
453            eye_height_from_head_bone: None,
454            splice_head: None,
455            displaced_head: None,
456            left_hand_bone_id: None,
457            right_hand_bone_id: None,
458            left_hand_raw_target_id: None,
459            right_hand_raw_target_id: None,
460            left_hand_visual_target_id: None,
461            right_hand_visual_target_id: None,
462            body_pipeline_id: None,
463            splice_camera_bone: None,
464            skip_body_pipeline: false,
465            ik_debug: false,
466            head_ik_eye_height: None,
467            neck_bone: Some("J_Bip_C_Neck".to_string()),
468            model_root_id: None,
469            model_root_local_y: 0.0,
470            neck_bone_id: None,
471            neck_rest_translation: None,
472            hand_grip_rotation_left: None,
473            hand_grip_rotation_right: None,
474            calibrate_hand_transforms: false,
475            component: None,
476        }
477    }
478}
479
480impl Component for AvatarControlComponent {
481    fn name(&self) -> &'static str {
482        "avatar_control"
483    }
484
485    fn set_id(&mut self, id: ComponentId) {
486        self.component = Some(id);
487    }
488
489    fn init(&mut self, emit: &mut dyn crate::engine::ecs::SignalEmitter, component: ComponentId) {
490        emit.push_intent_now(
491            component,
492            crate::engine::ecs::IntentValue::RegisterAvatarControl {
493                component_ids: vec![component],
494            },
495        );
496    }
497
498    fn as_any(&self) -> &dyn std::any::Any {
499        self
500    }
501    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
502        self
503    }
504
505    fn to_mms_ast(
506        &self,
507        _world: &crate::engine::ecs::World,
508    ) -> crate::scripting::ast::ComponentExpression {
509        use crate::engine::ecs::component::ce_helpers::*;
510        let mut c = ce("AvatarControl")
511            .with_call("head_bone", vec![s(&self.head_bone)])
512            .with_call(
513                "body_yaw_threshold",
514                vec![num(self.body_yaw_threshold as f64)],
515            )
516            .with_call("body_yaw_rate", vec![num(self.body_yaw_rate as f64)]);
517        if let Some(b) = &self.left_hand_bone {
518            c = c.with_call("left_hand_bone", vec![s(b)]);
519        }
520        if let Some(b) = &self.right_hand_bone {
521            c = c.with_call("right_hand_bone", vec![s(b)]);
522        }
523        if let Some(b) = &self.left_upper_arm_bone {
524            c = c.with_call("left_upper_arm_bone", vec![s(b)]);
525        }
526        if let Some(b) = &self.left_lower_arm_bone {
527            c = c.with_call("left_lower_arm_bone", vec![s(b)]);
528        }
529        if let Some(b) = &self.right_upper_arm_bone {
530            c = c.with_call("right_upper_arm_bone", vec![s(b)]);
531        }
532        if let Some(b) = &self.right_lower_arm_bone {
533            c = c.with_call("right_lower_arm_bone", vec![s(b)]);
534        }
535        if self.left_arm_pole_direction != [-1.0, 0.0, -1.0] {
536            let d = self.left_arm_pole_direction;
537            c = c.with_call(
538                "left_arm_pole_direction",
539                vec![array(vec![
540                    num(d[0] as f64),
541                    num(d[1] as f64),
542                    num(d[2] as f64),
543                ])],
544            );
545        }
546        if self.right_arm_pole_direction != [1.0, 0.0, -1.0] {
547            let d = self.right_arm_pole_direction;
548            c = c.with_call(
549                "right_arm_pole_direction",
550                vec![array(vec![
551                    num(d[0] as f64),
552                    num(d[1] as f64),
553                    num(d[2] as f64),
554                ])],
555            );
556        }
557        if self.forward_plus_z_overridden && self.forward_plus_z {
558            c = c.with_call("forward_plus_z", vec![]);
559        }
560        if self.initial_body_yaw_overridden {
561            c = c.with_call("initial_yaw", vec![num(self.initial_body_yaw as f64)]);
562        }
563        if self.ik_debug {
564            c = c.with_call("ik_debug", vec![]);
565        }
566        if self.calibrate_hand_transforms {
567            c = c.with_call("calibrate_hand_transforms", vec![]);
568        }
569        if let Some(factor) = self.hand_rotation_smoothing {
570            c = c.with_call("hand_rotation_smoothing", vec![num(factor as f64)]);
571        }
572        if let Some(b) = &self.camera_bone {
573            c = c.with_call("camera_bone", vec![s(b)]);
574        }
575        if let Some(h) = self.avatar_height {
576            c = c.with_call("avatar_height", vec![num(h as f64)]);
577        }
578        if let Some(dy) = self.eye_height_from_head_bone {
579            c = c.with_call("eye_height_from_head_bone", vec![num(dy as f64)]);
580        }
581        if let Some(dy) = self.head_ik_eye_height {
582            c = c.with_call("head_ik_eye_height", vec![num(dy as f64)]);
583        }
584        if let Some(q) = self.hand_grip_rotation_left {
585            c = c.with_call(
586                "hand_grip_rotation_left",
587                vec![array(vec![
588                    num(q[0] as f64),
589                    num(q[1] as f64),
590                    num(q[2] as f64),
591                    num(q[3] as f64),
592                ])],
593            );
594        }
595        if let Some(q) = self.hand_grip_rotation_right {
596            c = c.with_call(
597                "hand_grip_rotation_right",
598                vec![array(vec![
599                    num(q[0] as f64),
600                    num(q[1] as f64),
601                    num(q[2] as f64),
602                    num(q[3] as f64),
603                ])],
604            );
605        }
606        if let Some(b) = &self.hips_bone {
607            c = c.with_call("hips_bone", vec![s(b)]);
608        }
609        c
610    }
611}