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}