A 3D game engine from scratch.
1// (c) 2020 Vlad-Stefan Harbuz <vlad@vladh.net>
2
3#include "util.hpp"
4#include "logs.hpp"
5#include "engine.hpp"
6#include "anim.hpp"
7#include "intrinsics.hpp"
8
9
10anim::State *anim::state = nullptr;
11
12
13bool
14anim::is_animation_component_valid(anim::Component *animation_component)
15{
16 return animation_component->n_bones > 0 &&
17 animation_component->n_animations > 0;
18}
19
20
21u32
22anim::push_to_bone_matrix_pool()
23{
24 return anim::state->bone_matrix_pool.n_bone_matrix_sets++;
25}
26
27
28m4 *
29anim::get_bone_matrix(u32 idx, u32 idx_bone, u32 idx_anim_key)
30{
31 return anim::state->bone_matrix_pool.bone_matrices[
32 idx * MAX_N_ANIM_KEYS * MAX_N_BONES +
33 idx_anim_key * MAX_N_BONES +
34 idx_bone
35 ];
36}
37
38
39void
40anim::update()
41{
42 each (animation_component, *get_components()) {
43 if (!is_animation_component_valid(animation_component)) {
44 continue;
45 }
46
47 Animation *animation = &animation_component->animations[0];
48
49 range_named (idx_bone, 0, animation_component->n_bones) {
50 Bone *bone = &animation_component->bones[idx_bone];
51
52 // If we have no anim keys, just return the identity matrix.
53 if (bone->n_anim_keys == 0) {
54 animation_component->bone_matrices[idx_bone] = m4(1.0f);
55
56 // If we only have one anim key, just return that.
57 } else if (bone->n_anim_keys == 1) {
58 animation_component->bone_matrices[idx_bone] = *get_bone_matrix(
59 animation->idx_bone_matrix_set, idx_bone, 0);
60
61 // If we have multiple anim keys, find the right ones and interpolate.
62 } else {
63 f64 animation_timepoint = fmod(engine::get_t(), animation->duration);
64
65 u32 idx_anim_key = get_bone_matrix_anim_key_for_timepoint(
66 animation_component,
67 animation_timepoint, animation->idx_bone_matrix_set,
68 idx_bone);
69
70 f64 t0 = *get_bone_matrix_time(
71 animation->idx_bone_matrix_set, idx_bone, idx_anim_key);
72 m4 transform_t0 = *get_bone_matrix(
73 animation->idx_bone_matrix_set, idx_bone, idx_anim_key);
74
75 f64 t1 = *get_bone_matrix_time(
76 animation->idx_bone_matrix_set, idx_bone, idx_anim_key + 1);
77 m4 transform_t1 = *get_bone_matrix(
78 animation->idx_bone_matrix_set, idx_bone, idx_anim_key + 1);
79
80 f32 lerp_factor = (f32)((animation_timepoint - t0) / (t1 - t0));
81
82 // NOTE: This is probably bad if we have scaling in our transform?
83 m4 interpolated_matrix =
84 (transform_t0 * (1.0f - lerp_factor)) + (transform_t1 * lerp_factor);
85
86 animation_component->bone_matrices[idx_bone] = interpolated_matrix;
87 }
88 }
89 }
90}
91
92
93void
94anim::make_bone_matrices_for_animation_bone(
95 anim::Component *animation_component,
96 aiNodeAnim *ai_channel,
97 u32 idx_animation,
98 u32 idx_bone
99) {
100 assert(ai_channel->mNumPositionKeys == ai_channel->mNumRotationKeys);
101 assert(ai_channel->mNumPositionKeys == ai_channel->mNumScalingKeys);
102
103 Bone *bone = &animation_component->bones[idx_bone];
104 bone->n_anim_keys = ai_channel->mNumPositionKeys;
105
106 range_named (idx_anim_key, 0, bone->n_anim_keys) {
107 assert(ai_channel->mPositionKeys[idx_anim_key].mTime ==
108 ai_channel->mRotationKeys[idx_anim_key].mTime);
109 assert(ai_channel->mPositionKeys[idx_anim_key].mTime ==
110 ai_channel->mScalingKeys[idx_anim_key].mTime);
111 f64 anim_key_time = ai_channel->mPositionKeys[idx_anim_key].mTime;
112
113 m4 *bone_matrix = get_bone_matrix(
114 animation_component->animations[idx_animation].idx_bone_matrix_set,
115 idx_bone, idx_anim_key);
116
117 f64 *time = get_bone_matrix_time(
118 animation_component->animations[idx_animation].idx_bone_matrix_set,
119 idx_bone, idx_anim_key);
120
121 m4 parent_transform = m4(1.0f);
122
123 if (idx_bone > 0) {
124 parent_transform = *get_bone_matrix(
125 animation_component->animations[idx_animation].idx_bone_matrix_set,
126 bone->idx_parent, idx_anim_key);
127 }
128
129 m4 translation = glm::translate(m4(1.0f),
130 util::aiVector3D_to_glm(&ai_channel->mPositionKeys[idx_anim_key].mValue));
131 m4 rotation = glm::toMat4(normalize(
132 util::aiQuaternion_to_glm(&ai_channel->mRotationKeys[idx_anim_key].mValue)
133 ));
134 m4 scaling = glm::scale(m4(1.0f),
135 util::aiVector3D_to_glm(&ai_channel->mScalingKeys[idx_anim_key].mValue));
136
137 m4 anim_transform = translation * rotation * scaling;
138 *bone_matrix = parent_transform * anim_transform;
139 *time = anim_key_time;
140 }
141}
142
143
144anim::Component *
145anim::find_animation_component(spatial::Component *spatial_component)
146{
147 anim::Component *animation_component = get_component(spatial_component->entity_handle);
148
149 if (is_animation_component_valid(animation_component)) {
150 return animation_component;
151 }
152
153 if (spatial_component->parent_entity_handle != entities::NO_ENTITY_HANDLE) {
154 spatial::Component *parent = spatial::get_component(spatial_component->parent_entity_handle);
155 return find_animation_component(parent);
156 }
157
158 return nullptr;
159}
160
161
162Array<anim::Component> *
163anim::get_components()
164{
165 return &anim::state->components;
166}
167
168
169anim::Component *
170anim::get_component(entities::Handle entity_handle)
171{
172 return anim::state->components[entity_handle];
173}
174
175
176void
177anim::init(anim::State *anim_state, memory::Pool *asset_memory_pool)
178{
179 anim::state = anim_state;
180 anim::state->bone_matrix_pool.bone_matrices = Array<m4>(asset_memory_pool,
181 MAX_N_ANIMATED_MODELS * MAX_N_BONES * MAX_N_ANIMATIONS * MAX_N_ANIM_KEYS,
182 "bone_matrices", true);
183 anim::state->bone_matrix_pool.times = Array<f64>(asset_memory_pool,
184 MAX_N_ANIMATED_MODELS * MAX_N_BONES * MAX_N_ANIMATIONS * MAX_N_ANIM_KEYS,
185 "bone_matrix_times", true);
186 anim::state->components = Array<anim::Component>(
187 asset_memory_pool, MAX_N_ENTITIES, "animation_components", true, 1);
188}
189
190
191f64 *
192anim::get_bone_matrix_time(
193 u32 idx,
194 u32 idx_bone,
195 u32 idx_anim_key
196) {
197 return anim::state->bone_matrix_pool.times[
198 idx * MAX_N_ANIM_KEYS * MAX_N_BONES +
199 idx_anim_key * MAX_N_BONES +
200 idx_bone
201 ];
202}
203
204
205u32
206anim::get_bone_matrix_anim_key_for_timepoint(
207 anim::Component *animation_component,
208 f64 animation_timepoint,
209 u32 idx_bone_matrix_set,
210 u32 idx_bone
211) {
212 Bone *bone = &animation_component->bones[idx_bone];
213 assert(bone->n_anim_keys > 1);
214 u32 idx_anim_key = bone->last_anim_key;
215 do {
216 f64 t0 = *get_bone_matrix_time(idx_bone_matrix_set, idx_bone, idx_anim_key);
217 f64 t1 = *get_bone_matrix_time(idx_bone_matrix_set, idx_bone, idx_anim_key + 1);
218 if (animation_timepoint > t0 && animation_timepoint < t1) {
219 bone->last_anim_key = idx_anim_key;
220 return idx_anim_key;
221 }
222 idx_anim_key++;
223 assert(idx_anim_key < bone->n_anim_keys);
224 if (idx_anim_key == bone->n_anim_keys - 1) {
225 idx_anim_key = 0;
226 }
227 } while (idx_anim_key != bone->last_anim_key);
228 logs::fatal("Could not find anim key.");
229 return 0;
230}