Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
IdleClip.cpp
Go to the documentation of this file.
2
3#include <cmath>
4#include <vector>
5
6#include <glm/gtc/quaternion.hpp>
7#include <glm/vec3.hpp>
8
10#include "animation/Skeleton.h"
11
12namespace mnd
13{
14
15namespace
16{
17
18constexpr u32 kSamplesPerSecond = 30; ///< Sampling density. 30 Hz is plenty for a slow idle.
19
20struct IdleSpec
21{
22 const char *boneName;
23 glm::vec3 axis; ///< Rotation axis in bone-local space.
24 f32 amplitudeRad; ///< Peak rotation in radians.
25 f32 phaseOffset; ///< Phase shift in cycles ([0,1)) — desyncs left/right for organic feel.
26};
27
28/// Canonical FPS-arms bones we know how to drive. Each entry is opt-in: if the
29/// bone is missing in the skeleton we silently skip it. Amplitudes are tuned
30/// small (1–2.5°) to read as "alive" without looking jittery.
31const IdleSpec kIdleSpecs[] = {
32 // Shoulders breathe in/out around X (lift/drop).
33 {"shoulder.r", glm::vec3(1, 0, 0), 0.025f, 0.00f},
34 {"shoulder.l", glm::vec3(1, 0, 0), 0.025f, 0.05f},
35 // Biceps follow with a tiny counter-rotation.
36 {"bicep.r", glm::vec3(0, 0, 1), -0.018f, 0.07f},
37 {"bicep.l", glm::vec3(0, 0, 1), 0.018f, 0.12f},
38 // Forearms get a subtle yaw (weapon hand sway).
39 {"forearm.r", glm::vec3(0, 1, 0), 0.022f, 0.10f},
40 {"forearm.l", glm::vec3(0, 1, 0), -0.022f, 0.15f},
41 // Wrists add the highest-frequency wobble — half the period for life.
42 {"wrist.r", glm::vec3(1, 0, 0), 0.030f, 0.20f},
43 {"wrist.l", glm::vec3(1, 0, 0), 0.030f, 0.25f},
44 // Whole rig root: tiny pitch giving the camera-relative bob.
45 {"root", glm::vec3(1, 0, 0), 0.012f, 0.00f},
46};
47
48} // namespace
49
50std::shared_ptr<SkeletalAnimationClip> MakeBreathingIdleClip(const Skeleton &skeleton, f32 duration, f32 ampScale)
51{
52 auto clip = std::make_shared<SkeletalAnimationClip>();
53 clip->name = "idle";
54 clip->duration = duration;
55 clip->looping = true;
56
57 if (duration <= 0.0f || skeleton.Size() == 0)
58 {
59 return clip;
60 }
61
62 const u32 sampleCount = static_cast<u32>(duration * kSamplesPerSecond) + 1;
63
64 for (const auto &spec : kIdleSpecs)
65 {
66 i32 boneIx = skeleton.FindBoneIndex(spec.boneName);
67 if (boneIx < 0)
68 {
69 continue;
70 }
71
72 const auto &bone = skeleton.GetBones()[boneIx];
73
74 BoneTrack track;
75 track.boneIndex = boneIx;
76 track.rotations.reserve(sampleCount);
77
78 for (u32 s = 0; s < sampleCount; ++s)
79 {
80 f32 t = static_cast<f32>(s) / static_cast<f32>(kSamplesPerSecond);
81 // Two phases: shoulders/biceps breathe at 1× duration, wrists at 2× — looks alive.
82 f32 cyclesPerLoop = 1.0f;
83 f32 phase = (t / duration + spec.phaseOffset) * cyclesPerLoop;
84 f32 angle = spec.amplitudeRad * ampScale * std::sin(phase * 6.283185307f);
85
86 glm::quat anim = glm::angleAxis(angle, glm::normalize(spec.axis));
87 // Compose with bind rotation so shoulders etc. keep their rest orientation.
88 glm::quat finalRot = bone.bindRot * anim;
89
90 track.rotations.push_back({t, finalRot});
91 }
92
93 clip->tracks.push_back(std::move(track));
94 }
95
96 return clip;
97}
98
99} // namespace mnd
f32 phaseOffset
Phase shift in cycles ([0,1)) — desyncs left/right for organic feel.
Definition IdleClip.cpp:25
f32 amplitudeRad
Peak rotation in radians.
Definition IdleClip.cpp:24
const char * boneName
Definition IdleClip.cpp:22
glm::vec3 axis
Rotation axis in bone-local space.
Definition IdleClip.cpp:23
Procedural skeletal idle clip — used when an asset ships rigged but without baked animations....
Per-bone TRS keyframe tracks driving a Skeleton.
Bone hierarchy + bind/offset matrices for GPU-skinned meshes.
i32 FindBoneIndex(const std::string &name) const
Definition Skeleton.cpp:21
const std::vector< Bone > & GetBones() const
Definition Skeleton.h:44
usize Size() const
Definition Skeleton.h:42
float f32
32-bit IEEE float — the standard GL scalar type.
Definition Types.h:40
int32_t i32
Signed 32-bit integer — used for GL enums and sizes.
Definition Types.h:37
uint32_t u32
Unsigned 32-bit integer — also the GL index type.
Definition Types.h:32
std::shared_ptr< SkeletalAnimationClip > MakeBreathingIdleClip(const Skeleton &skeleton, f32 duration, f32 ampScale)
Build a looping breathing/sway clip targeting whichever of the canonical arm bones exist in skeleton....
Definition IdleClip.cpp:50
i32 boneIndex
Index into Skeleton::bones.
std::vector< KeyFrameQuat > rotations