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