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
use crate::{
    core::{
        animation::components::{
            apply_base_pose, bind_id, bind_ids, blend, clip_duration, freeze_at_percentage,
            freeze_at_time, is_animation_player, looping, mask_bind_ids, mask_weights,
            play_clip_from_url, retarget_animation_scaled, retarget_model_from_url, start_time,
        },
        app::components::name,
        hierarchy::components::{children, parent},
    },
    entity,
    prelude::{epoch_time, Entity, EntityId},
};
use std::time::Duration;

/// This plays animations, and can handle blending and masking of animations together to create
/// complex effects. A single animation player can be attached to multiple entities in the scene.
///
/// This is just a reference to an entity which lives in the ecs. You need to call `despawn` to
/// remove it.
#[derive(Debug, Clone, Copy)]
pub struct AnimationPlayerRef(pub EntityId);
impl AnimationPlayerRef {
    /// Create a new animation player, with `root` as the currently playing node
    pub fn new(root: impl AsRef<AnimationNodeRef>) -> Self {
        let root: &AnimationNodeRef = root.as_ref();
        let player = Entity::new()
            .with(is_animation_player(), ())
            .with(children(), vec![root.0])
            .with(name(), "Animation player".to_string())
            .spawn();
        entity::add_component(root.0, parent(), player);
        Self(player)
    }
    fn root(&self) -> Option<AnimationNodeRef> {
        if let Some(children) = entity::get_component(self.0, children()) {
            children.first().copied().map(AnimationNodeRef)
        } else {
            None
        }
    }
    /// Replaces the current root node of the animation player with a new node
    pub fn play(&self, node: impl AsRef<AnimationNodeRef>) -> Option<AnimationNodeRef> {
        let old_root = self.root();
        let new_root: &AnimationNodeRef = node.as_ref();
        entity::add_component(self.0, children(), vec![new_root.0]);
        entity::add_component(new_root.0, parent(), self.0);
        old_root
    }
    /// Despawn this animation player.
    /// Note that dropping this player won't despawn it automatically; only call this method will despawn it.
    pub fn despawn(self) {
        if let Some(root) = self.root() {
            root.despawn();
        }
        entity::despawn(self.0);
    }
}

/// An animation node. Used in the animation player.
#[derive(Debug, Clone, Copy)]
pub struct AnimationNodeRef(EntityId);
impl AnimationNodeRef {
    /// Get the entity id of this node
    pub fn get_entity_id(&self) -> EntityId {
        self.0
    }
    /// Use an existing node
    pub fn from_entity(entity: EntityId) -> Self {
        Self(entity)
    }
    /// Delete this node and its children
    pub fn despawn(self) {
        for c in entity::get_component(self.0, children()).unwrap_or_default() {
            Self::from_entity(c).despawn();
        }
        entity::despawn(self.0);
    }
}

/// Play mode for animation nodes
pub enum PlayMode {
    /// Play the animation
    Play {
        /// Global time for the start of the animation
        start_time: Duration,
    },
    /// Freeze the animation at time
    FreezeAtTime {
        /// Time in the animation we want to freeze at
        time: f32,
    },
    /// Freeze the animation at time = percentage * duration
    FreezeAtPercentage {
        /// Percentage into the animation that we want to freeze at
        percentage: f32,
    },
}
impl PlayMode {
    /// Play an animation
    pub fn play() -> Self {
        Self::Play {
            start_time: epoch_time(),
        }
    }
}

/// Play clip from url animation node.
/// This is an animation node which can be plugged into an animation player or other animation nodes.
///
/// This is just a reference to an entity which lives in the ecs. You need to call `despawn` to
/// remove it.
#[derive(Debug, Clone, Copy)]
pub struct PlayClipFromUrlNodeRef(pub AnimationNodeRef);
impl PlayClipFromUrlNodeRef {
    /// Create a new node.
    pub fn new(url: impl Into<String>) -> Self {
        let node = Entity::new()
            .with(play_clip_from_url(), url.into())
            .with(name(), "Play clip from URL".to_string())
            .with(looping(), true)
            .with(start_time(), epoch_time())
            .spawn();
        Self(AnimationNodeRef(node))
    }
    /// Use an existing node
    pub fn from_entity(entity: EntityId) -> Self {
        Self(AnimationNodeRef::from_entity(entity))
    }
    /// Set if the animation should loop or not
    pub fn looping(&self, value: bool) {
        entity::add_component(self.0 .0, looping(), value);
    }
    /// Set how the animation is played
    pub fn set_play_mode(&self, mode: PlayMode) {
        match mode {
            PlayMode::Play { start_time: time } => {
                entity::add_component(self.0 .0, start_time(), time);
                entity::remove_component(self.0 .0, freeze_at_time());
                entity::remove_component(self.0 .0, freeze_at_percentage());
            }
            PlayMode::FreezeAtTime { time } => {
                entity::add_component(self.0 .0, freeze_at_time(), time);
                entity::remove_component(self.0 .0, start_time());
                entity::remove_component(self.0 .0, freeze_at_percentage());
            }
            PlayMode::FreezeAtPercentage { percentage } => {
                entity::add_component(self.0 .0, freeze_at_percentage(), percentage);
                entity::remove_component(self.0 .0, start_time());
                entity::remove_component(self.0 .0, freeze_at_time());
            }
        }
    }
    /// Start playing the animation from the beginning
    pub fn restart(&self) {
        self.set_play_mode(PlayMode::Play {
            start_time: epoch_time(),
        });
    }
    /// Set up retargeting
    pub fn set_retargeting(&self, retargeting: AnimationRetargeting) {
        match retargeting {
            AnimationRetargeting::None => {
                entity::remove_component(self.0 .0, retarget_model_from_url());
            }
            AnimationRetargeting::Skeleton { model_url } => {
                entity::remove_component(self.0 .0, retarget_animation_scaled());
                entity::add_component(self.0 .0, retarget_model_from_url(), model_url);
            }
            AnimationRetargeting::AnimationScaled {
                normalize_hip,
                model_url,
            } => {
                entity::add_component(self.0 .0, retarget_animation_scaled(), normalize_hip);
                entity::add_component(self.0 .0, retarget_model_from_url(), model_url);
            }
        }
    }
    /// If true, the base pose from the model of the animation clip will be applied to the animation
    ///
    /// Some animations will only work if the base pose of the character is the same as
    /// the animations base pose, so we apply the pose from the animations model to make sure they
    /// correspond
    ///
    /// This exists mostly because some FBX animations have pre-rotations, and to apply them to
    /// character models which don't have the same pre-rotations we need to make sure they're up to sync
    ///
    /// I.e. this is mostly relevant for retargeting
    pub fn apply_base_pose(&self, value: bool) {
        if value {
            entity::add_component(self.0 .0, apply_base_pose(), ());
        } else {
            entity::remove_component(self.0 .0, apply_base_pose());
        }
    }
    /// Returns None if the duration hasn't been loaded yet
    pub fn peek_clip_duration(&self) -> Option<f32> {
        entity::get_component(self.0 .0, clip_duration())
    }
    /// Returns the duration of this clip. This is async because it needs to wait for the clip to load before the duration can be returned.
    pub async fn clip_duration(&self) -> f32 {
        entity::wait_for_component(self.0 .0, clip_duration())
            .await
            .unwrap_or_default()
    }
    /// Returns None if the clip hasn't been loaded yet
    pub fn peek_bind_ids(&self) -> Option<Vec<String>> {
        entity::get_component(self.0 .0, bind_ids())
    }
    /// Returns the bind ids of this clip. This is async because it needs to wait for the clip to load before the bind ids can be returned.
    pub async fn bind_ids(&self) -> Vec<String> {
        entity::wait_for_component(self.0 .0, bind_ids())
            .await
            .unwrap_or_default()
    }
    /// Wait until the clip has been loaded
    pub async fn wait_for_load(&self) {
        self.clip_duration().await;
    }
}
impl AsRef<AnimationNodeRef> for PlayClipFromUrlNodeRef {
    fn as_ref(&self) -> &AnimationNodeRef {
        &self.0
    }
}

/// Blend animation node.
/// This is an animation node which can be plugged into an animation player or other animation nodes.
///
/// This is just a reference to an entity which lives in the ecs. You need to call `despawn` to
/// remove it.
#[derive(Debug, Clone, Copy)]
pub struct BlendNodeRef(pub AnimationNodeRef);
impl BlendNodeRef {
    /// Create a new blend animation node.
    ///
    /// If the weight is 0, only the left animation will play.
    /// If the weight is 1, only the right animation will play.
    /// Values in between blend between the two animations.
    pub fn new(
        left: impl AsRef<AnimationNodeRef>,
        right: impl AsRef<AnimationNodeRef>,
        weight: f32,
    ) -> Self {
        let left: &AnimationNodeRef = left.as_ref();
        let right: &AnimationNodeRef = right.as_ref();
        let node = Entity::new()
            .with(blend(), weight)
            .with(name(), "Blend".to_string())
            .with(children(), vec![left.0, right.0])
            .spawn();
        entity::add_component(left.0, parent(), node);
        entity::add_component(right.0, parent(), node);
        Self(AnimationNodeRef(node))
    }
    /// Use an existing node
    pub fn from_entity(entity: EntityId) -> Self {
        Self(AnimationNodeRef::from_entity(entity))
    }
    /// Set the weight of this blend node.
    ///
    /// If the weight is 0, only the left animation will play.
    /// If the weight is 1, only the right animation will play.
    /// Values in between blend between the two animations.
    pub fn set_weight(&self, weight: f32) {
        entity::set_component(self.0 .0, blend(), weight);
    }
    /// Sets the mask of this blend node.
    ///
    /// For example `blend_node.set_mask(vec![("LeftLeg".to_string(), 1.)])` means
    /// that the LeftLeg is always controlled by the right animation.
    pub fn set_mask(&self, weights: Vec<(BindId, f32)>) {
        let (bind_ids, weights): (Vec<_>, Vec<_>) = weights
            .into_iter()
            .map(|(a, b)| (a.as_str().to_string(), b))
            .unzip();
        entity::add_component(self.0 .0, mask_bind_ids(), bind_ids);
        entity::add_component(self.0 .0, mask_weights(), weights);
    }
    /// Sets a mask value to all bones of a humanoids lower body
    pub fn set_mask_humanoid_lower_body(&self, weight: f32) {
        self.set_mask(
            BindId::HUMANOID_LOWER_BODY
                .iter()
                .map(|x| ((*x).clone(), weight))
                .collect(),
        );
    }
    /// Sets a mask value to all bones of a humanoids upper body
    pub fn set_mask_humanoid_upper_body(&self, weight: f32) {
        self.set_mask(
            BindId::HUMANOID_UPPER_BODY
                .iter()
                .map(|x| ((*x).clone(), weight))
                .collect(),
        );
    }
}
impl AsRef<AnimationNodeRef> for BlendNodeRef {
    fn as_ref(&self) -> &AnimationNodeRef {
        &self.0
    }
}

/// Animation retargeting configuration.
#[derive(Debug, Clone)]
pub enum AnimationRetargeting {
    /// Bone Translation comes from the animation data, unchanged.
    None,
    /// Bone Translation comes from the Target Skeleton's bind pose.
    Skeleton {
        /// Model url which the animation will be retargeted to.
        model_url: String,
    },
    /// Bone translation comes from the animation data, but is scaled by the Skeleton's proportions.
    /// This is the ratio between the bone length of the Target Skeleton (the skeleton the animation
    /// is being played on), and the Source Skeleton (the skeleton the animation was authored for).
    AnimationScaled {
        /// Rotates the Hips bone based on the difference between the rotation the animation models root and the retarget animations root
        normalize_hip: bool,
        /// Model url which the animation will be retargeted to.
        model_url: String,
    },
}
impl Default for AnimationRetargeting {
    fn default() -> Self {
        Self::None
    }
}

/// Get the bone entity from the bind_id; for example "LeftFoot"
pub fn get_bone_by_bind_id(entity: EntityId, bind_id: &BindId) -> Option<EntityId> {
    if let Some(bid) = entity::get_component(entity, self::bind_id()) {
        if bid == bind_id.as_str() {
            return Some(entity);
        }
    }
    if let Some(childs) = entity::get_component(entity, children()) {
        for c in childs {
            if let Some(bid) = get_bone_by_bind_id(c, bind_id) {
                return Some(bid);
            }
        }
    }
    None
}

/// Valid bind ids
#[derive(Debug, Clone)]
pub enum BindId {
    // Lower body for convenience
    /// Hips
    Hips,
    /// LeftFoot
    LeftFoot,
    /// LeftLeg
    LeftLeg,
    /// LeftToeBase
    LeftToeBase,
    /// LeftUpLeg
    LeftUpLeg,
    /// RightFoot
    RightFoot,
    /// RightLeg
    RightLeg,
    /// RightToeBase
    RightToeBase,
    /// RightUpLeg
    RightUpLeg,
    // Upper
    /// Head
    Head,
    /// LeftArm
    LeftArm,
    /// LeftForeArm
    LeftForeArm,
    /// LeftHand
    LeftHand,
    /// LeftHandIndex1
    LeftHandIndex1,
    /// LeftHandIndex2
    LeftHandIndex2,
    /// LeftHandIndex3
    LeftHandIndex3,
    /// LeftHandMiddle1
    LeftHandMiddle1,
    /// LeftHandMiddle2
    LeftHandMiddle2,
    /// LeftHandMiddle3
    LeftHandMiddle3,
    /// LeftHandPinky1
    LeftHandPinky1,
    /// LeftHandPinky2
    LeftHandPinky2,
    /// LeftHandPinky3
    LeftHandPinky3,
    /// LeftHandRing1
    LeftHandRing1,
    /// LeftHandRing2
    LeftHandRing2,
    /// LeftHandRing3
    LeftHandRing3,
    /// LeftHandThumb1
    LeftHandThumb1,
    /// LeftHandThumb2
    LeftHandThumb2,
    /// LeftHandThumb3
    LeftHandThumb3,
    /// LeftShoulder
    LeftShoulder,
    /// Neck
    Neck,
    /// RightArm
    RightArm,
    /// RightForeArm
    RightForeArm,
    /// RightHand
    RightHand,
    /// RightHandIndex1
    RightHandIndex1,
    /// RightHandIndex2
    RightHandIndex2,
    /// RightHandIndex3
    RightHandIndex3,
    /// RightHandMiddle1
    RightHandMiddle1,
    /// RightHandMiddle2
    RightHandMiddle2,
    /// RightHandMiddle3
    RightHandMiddle3,
    /// RightHandPinky1
    RightHandPinky1,
    /// RightHandPinky2
    RightHandPinky2,
    /// RightHandPinky3
    RightHandPinky3,
    /// RightHandRing1
    RightHandRing1,
    /// RightHandRing2
    RightHandRing2,
    /// RightHandRing3
    RightHandRing3,
    /// RightHandThumb1
    RightHandThumb1,
    /// RightHandThumb2
    RightHandThumb2,
    /// RightHandThumb3
    RightHandThumb3,
    /// RightShoulder
    RightShoulder,
    /// Spine
    Spine,
    /// Spine1
    Spine1,
    /// Spine2
    Spine2,

    /// Custom bind id
    Custom(String),
}
impl BindId {
    /// Get a string representation of this bind id
    pub fn as_str(&self) -> &str {
        match self {
            BindId::Hips => "Hips",
            BindId::LeftFoot => "LeftFoot",
            BindId::LeftLeg => "LeftLeg",
            BindId::LeftToeBase => "LeftToeBase",
            BindId::LeftUpLeg => "LeftUpLeg",
            BindId::RightFoot => "RightFoot",
            BindId::RightLeg => "RightLeg",
            BindId::RightToeBase => "RightToeBase",
            BindId::RightUpLeg => "RightUpLeg",
            BindId::Head => "Head",
            BindId::LeftArm => "LeftArm",
            BindId::LeftForeArm => "LeftForeArm",
            BindId::LeftHand => "LeftHand",
            BindId::LeftHandIndex1 => "LeftHandIndex1",
            BindId::LeftHandIndex2 => "LeftHandIndex2",
            BindId::LeftHandIndex3 => "LeftHandIndex3",
            BindId::LeftHandMiddle1 => "LeftHandMiddle1",
            BindId::LeftHandMiddle2 => "LeftHandMiddle2",
            BindId::LeftHandMiddle3 => "LeftHandMiddle3",
            BindId::LeftHandPinky1 => "LeftHandPinky1",
            BindId::LeftHandPinky2 => "LeftHandPinky2",
            BindId::LeftHandPinky3 => "LeftHandPinky3",
            BindId::LeftHandRing1 => "LeftHandRing1",
            BindId::LeftHandRing2 => "LeftHandRing2",
            BindId::LeftHandRing3 => "LeftHandRing3",
            BindId::LeftHandThumb1 => "LeftHandThumb1",
            BindId::LeftHandThumb2 => "LeftHandThumb2",
            BindId::LeftHandThumb3 => "LeftHandThumb3",
            BindId::LeftShoulder => "LeftShoulder",
            BindId::Neck => "Neck",
            BindId::RightArm => "RightArm",
            BindId::RightForeArm => "RightForeArm",
            BindId::RightHand => "RightHand",
            BindId::RightHandIndex1 => "RightHandIndex1",
            BindId::RightHandIndex2 => "RightHandIndex2",
            BindId::RightHandIndex3 => "RightHandIndex3",
            BindId::RightHandMiddle1 => "RightHandMiddle1",
            BindId::RightHandMiddle2 => "RightHandMiddle2",
            BindId::RightHandMiddle3 => "RightHandMiddle3",
            BindId::RightHandPinky1 => "RightHandPinky1",
            BindId::RightHandPinky2 => "RightHandPinky2",
            BindId::RightHandPinky3 => "RightHandPinky3",
            BindId::RightHandRing1 => "RightHandRing1",
            BindId::RightHandRing2 => "RightHandRing2",
            BindId::RightHandRing3 => "RightHandRing3",
            BindId::RightHandThumb1 => "RightHandThumb1",
            BindId::RightHandThumb2 => "RightHandThumb2",
            BindId::RightHandThumb3 => "RightHandThumb3",
            BindId::RightShoulder => "RightShoulder",
            BindId::Spine => "Spine",
            BindId::Spine1 => "Spine1",
            BindId::Spine2 => "Spine2",
            BindId::Custom(string) => string,
        }
    }
    /// Bind-ids for a humanoid's lower body
    pub const HUMANOID_LOWER_BODY: [BindId; 9] = [
        BindId::Hips,
        BindId::LeftFoot,
        BindId::LeftLeg,
        BindId::LeftToeBase,
        BindId::LeftUpLeg,
        BindId::RightFoot,
        BindId::RightLeg,
        BindId::RightToeBase,
        BindId::RightUpLeg,
    ];

    /// Bind-ids for a humanoid's upper body
    pub const HUMANOID_UPPER_BODY: [BindId; 43] = [
        BindId::Head,
        BindId::LeftArm,
        BindId::LeftForeArm,
        BindId::LeftHand,
        BindId::LeftHandIndex1,
        BindId::LeftHandIndex2,
        BindId::LeftHandIndex3,
        BindId::LeftHandMiddle1,
        BindId::LeftHandMiddle2,
        BindId::LeftHandMiddle3,
        BindId::LeftHandPinky1,
        BindId::LeftHandPinky2,
        BindId::LeftHandPinky3,
        BindId::LeftHandRing1,
        BindId::LeftHandRing2,
        BindId::LeftHandRing3,
        BindId::LeftHandThumb1,
        BindId::LeftHandThumb2,
        BindId::LeftHandThumb3,
        BindId::LeftShoulder,
        BindId::Neck,
        BindId::RightArm,
        BindId::RightForeArm,
        BindId::RightHand,
        BindId::RightHandIndex1,
        BindId::RightHandIndex2,
        BindId::RightHandIndex3,
        BindId::RightHandMiddle1,
        BindId::RightHandMiddle2,
        BindId::RightHandMiddle3,
        BindId::RightHandPinky1,
        BindId::RightHandPinky2,
        BindId::RightHandPinky3,
        BindId::RightHandRing1,
        BindId::RightHandRing2,
        BindId::RightHandRing3,
        BindId::RightHandThumb1,
        BindId::RightHandThumb2,
        BindId::RightHandThumb3,
        BindId::RightShoulder,
        BindId::Spine,
        BindId::Spine1,
        BindId::Spine2,
    ];
}