A 3D game engine from scratch.
1// (c) 2020 Vlad-Stefan Harbuz <vlad@vladh.net>
2
3#include "../src_external/glad/glad.h"
4#include "../src_external/pstr.h"
5#include <assimp/cimport.h>
6#include <assimp/postprocess.h>
7#include "pack.hpp"
8#include "logs.hpp"
9#include "models.hpp"
10#include "debug.hpp"
11#include "util.hpp"
12#include "intrinsics.hpp"
13
14
15bool
16models::prepare_model_loader_and_check_if_done(ModelLoader *model_loader) {
17 if (model_loader->state == ModelLoaderState::initialized) {
18 if (pstr_starts_with(model_loader->model_path, "builtin:")) {
19 logs::error("Found model with builtin model_path for which no vertex data was loaded.");
20 return false;
21 }
22 tasks::push({
23 .fn = (tasks::TaskFn)load_model_from_file,
24 .argument_1 = (void*)model_loader,
25 });
26 model_loader->state = ModelLoaderState::mesh_data_being_loaded;
27 }
28
29 if (model_loader->state == ModelLoaderState::mesh_data_being_loaded) {
30 // Wait. The task will progress this for us.
31 }
32
33 if (model_loader->state == ModelLoaderState::mesh_data_loaded) {
34 for (u32 idx = 0; idx < model_loader->n_meshes; idx++) {
35 geom::Mesh *mesh = &model_loader->meshes[idx];
36 geom::setup_mesh_vertex_buffers(mesh, mesh->vertices, mesh->n_vertices, mesh->indices, mesh->n_indices);
37 memory::destroy_memory_pool(&mesh->temp_memory_pool);
38 }
39 model_loader->state = ModelLoaderState::vertex_buffers_set_up;
40 }
41
42 if (model_loader->state == ModelLoaderState::vertex_buffers_set_up) {
43 // Set material names for each mesh
44 range_named (idx_material, 0, model_loader->n_material_names) {
45 range_named (idx_mesh, 0, model_loader->n_meshes) {
46 geom::Mesh *mesh = &model_loader->meshes[idx_mesh];
47 u8 mesh_number = pack::get(&mesh->indices_pack, 0);
48 // For our model's mesh number `mesh_number`, we want to choose
49 // material `idx_mesh` such that `mesh_number == idx_mesh`, i.e.
50 // we choose the 4th material for mesh number 4.
51 // However, if we have more meshes than materials, the extra
52 // meshes all get material number 0.
53 if (
54 mesh_number == idx_material ||
55 (mesh_number >= model_loader->n_material_names && idx_material == 0)
56 ) {
57 pstr_copy(mesh->material_name, MAX_COMMON_NAME_LENGTH, model_loader->material_names[idx_material]);
58 }
59 }
60 }
61
62 model_loader->state = ModelLoaderState::complete;
63 }
64
65 if (model_loader->state == ModelLoaderState::complete) {
66 return true;
67 }
68
69 return false;
70}
71
72
73bool
74models::prepare_entity_loader_and_check_if_done(
75 EntityLoader *entity_loader,
76 ModelLoader *model_loader
77) {
78 if (entity_loader->state == EntityLoaderState::initialized) {
79 // Before we can create entities, we need this entity's models to have
80 // been loaded.
81 if (model_loader->state != ModelLoaderState::complete) {
82 return false;
83 }
84
85 spatial::Component *spatial_component = spatial::get_component(entity_loader->entity_handle);
86 *spatial_component = entity_loader->spatial_component;
87 spatial_component->entity_handle = entity_loader->entity_handle;
88
89 lights::Component *light_component = lights::get_component(entity_loader->entity_handle);
90 *light_component = entity_loader->light_component;
91 light_component->entity_handle = entity_loader->entity_handle;
92
93 behavior::Component *behavior_component = behavior::get_component(entity_loader->entity_handle);
94 *behavior_component = entity_loader->behavior_component;
95 behavior_component->entity_handle = entity_loader->entity_handle;
96
97 anim::Component *animation_component = anim::get_component(entity_loader->entity_handle);
98 *animation_component = model_loader->animation_component;
99 animation_component->entity_handle = entity_loader->entity_handle;
100
101 physics::Component *physics_component = physics::get_component(entity_loader->entity_handle);
102 *physics_component = entity_loader->physics_component;
103 physics_component->entity_handle = entity_loader->entity_handle;
104
105 // drawable::Component
106 if (model_loader->n_meshes == 1) {
107 drawable::Component *drawable_component = drawable::get_component(entity_loader->entity_handle);
108 assert(drawable_component);
109 *drawable_component = {
110 .entity_handle = entity_loader->entity_handle,
111 .mesh = model_loader->meshes[0],
112 .target_render_pass = entity_loader->render_pass,
113 };
114 } else if (model_loader->n_meshes > 1) {
115 for (u32 idx = 0; idx < model_loader->n_meshes; idx++) {
116 geom::Mesh *mesh = &model_loader->meshes[idx];
117
118 entities::Entity *child_entity = entities::add_entity_to_set(entity_loader->name);
119
120 if (spatial::is_spatial_component_valid(&entity_loader->spatial_component)) {
121 spatial::Component *child_spatial_component = spatial::get_component(child_entity->handle);
122 assert(child_spatial_component);
123 *child_spatial_component = {
124 .entity_handle = child_entity->handle,
125 .position = v3(0.0f),
126 .rotation = glm::angleAxis(radians(0.0f), v3(0.0f)),
127 .scale = v3(0.0f),
128 .parent_entity_handle = entity_loader->entity_handle,
129 };
130 }
131
132 drawable::Component *drawable_component = drawable::get_component(child_entity->handle);
133 assert(drawable_component);
134 *drawable_component = {
135 .entity_handle = child_entity->handle,
136 .mesh = *mesh,
137 .target_render_pass = entity_loader->render_pass,
138 };
139 }
140 }
141
142 entity_loader->state = EntityLoaderState::complete;
143 }
144
145 if (entity_loader->state == EntityLoaderState::complete) {
146 return true;
147 }
148
149 return false;
150}
151
152
153bool
154models::is_model_loader_valid(ModelLoader *model_loader)
155{
156 return model_loader->state != ModelLoaderState::empty;
157}
158
159
160bool
161models::is_entity_loader_valid(EntityLoader *entity_loader)
162{
163 return entity_loader->state != EntityLoaderState::empty;
164}
165
166
167void
168models::add_material_to_model_loader(
169 ModelLoader *model_loader,
170 char const *material_name
171) {
172 pstr_copy(
173 model_loader->material_names[model_loader->n_material_names++],
174 MAX_COMMON_NAME_LENGTH,
175 material_name
176 );
177}
178
179
180models::ModelLoader *
181models::init_model_loader(
182 ModelLoader *model_loader,
183 char const *model_path
184) {
185 assert(model_loader);
186 pstr_copy(model_loader->model_path, MAX_PATH, model_path);
187
188 model_loader->state = ModelLoaderState::initialized;
189
190 if (pstr_starts_with(model_path, "builtin:")) {
191 load_model_from_data(model_loader);
192 }
193
194 return model_loader;
195}
196
197
198models::EntityLoader *
199models::init_entity_loader(
200 EntityLoader *entity_loader,
201 const char *name,
202 const char *model_path,
203 drawable::Pass render_pass,
204 entities::Handle entity_handle
205) {
206 assert(entity_loader);
207 pstr_copy(entity_loader->name, MAX_COMMON_NAME_LENGTH, name);
208 pstr_copy(entity_loader->model_path, MAX_PATH, model_path);
209 entity_loader->render_pass = render_pass;
210 entity_loader->entity_handle = entity_handle;
211 // TODO: Can we move this to constructor?
212 // If so, can we do so for other init_*() methods?
213 entity_loader->state = EntityLoaderState::initialized;
214 return entity_loader;
215}
216
217
218bool
219models::is_bone_only_node(aiNode *node)
220{
221 if (node->mNumMeshes > 0) {
222 return false;
223 }
224 bool have_we_found_it = true;
225 range (0, node->mNumChildren) {
226 if (!is_bone_only_node(node->mChildren[idx])) {
227 have_we_found_it = false;
228 }
229 }
230 return have_we_found_it;
231}
232
233
234aiNode *
235models::find_root_bone(const aiScene *scene)
236{
237 // NOTE: To find the root bone, we find the first-level node (direct child
238 // of root node) whose entire descendent tree has no meshes, including the
239 // leaf nodes. Is this a perfect way of finding the root bone? Probably
240 // not. Is it good enough? Sure looks like it! :)
241 aiNode *root_node = scene->mRootNode;
242
243 range (0, root_node->mNumChildren) {
244 aiNode *first_level_node = root_node->mChildren[idx];
245 if (is_bone_only_node(first_level_node)) {
246 return first_level_node;
247 }
248 }
249
250 return nullptr;
251}
252
253
254void
255models::add_bone_tree_to_animation_component(
256 anim::Component *animation_component,
257 aiNode *node,
258 u32 idx_parent
259) {
260 u32 idx_new_bone = animation_component->n_bones;
261 animation_component->bones[idx_new_bone] = {
262 .idx_parent = idx_parent,
263 // NOTE: offset is added later, since we don't have the aiBone at this stage.
264 };
265 pstr_copy(animation_component->bones[idx_new_bone].name, MAX_NODE_NAME_LENGTH, node->mName.C_Str());
266 animation_component->n_bones++;
267
268 range (0, node->mNumChildren) {
269 add_bone_tree_to_animation_component(animation_component, node->mChildren[idx], idx_new_bone);
270 }
271}
272
273
274void
275models::load_bones(
276 anim::Component *animation_component,
277 const aiScene *scene
278) {
279 aiNode *root_bone = find_root_bone(scene);
280
281 if (!root_bone) {
282 // No bones. Okay!
283 return;
284 }
285
286 // The root will just have its parent marked as itself, to avoid using
287 // a -1 index and so on. This is fine, because the root will always be
288 // index 0, so we can just disregard the parent if we're on index 0.
289 add_bone_tree_to_animation_component(animation_component, root_bone, 0);
290}
291
292
293void
294models::load_animations(
295 anim::Component *animation_component,
296 const aiScene *scene
297) {
298 m4 scene_root_transform = util::aimatrix4x4_to_glm(&scene->mRootNode->mTransformation);
299 m4 inverse_scene_root_transform = inverse(scene_root_transform);
300
301 animation_component->n_animations = scene->mNumAnimations;
302 range_named (idx_animation, 0, scene->mNumAnimations) {
303 anim::Animation *animation = &animation_component->animations[idx_animation];
304 aiAnimation *ai_animation = scene->mAnimations[idx_animation];
305
306 *animation = {
307 .duration = ai_animation->mDuration * ai_animation->mTicksPerSecond,
308 .idx_bone_matrix_set = anim::push_to_bone_matrix_pool(),
309 };
310 pstr_copy(animation->name, MAX_NODE_NAME_LENGTH, ai_animation->mName.C_Str());
311
312 // Calculate bone matrices.
313 // NOTE: We do not finalise the bone matrices at this stage!
314 // The matrices in local form are still needed for the children.
315 range_named(idx_bone, 0, animation_component->n_bones) {
316 anim::Bone *bone = &animation_component->bones[idx_bone];
317
318 u32 found_channel_idx = 0;
319 bool did_find_channel = false;
320
321 range_named (idx_channel, 0, ai_animation->mNumChannels) {
322 aiNodeAnim *ai_channel = ai_animation->mChannels[idx_channel];
323 if (pstr_eq(ai_channel->mNodeName.C_Str(), bone->name)) {
324 found_channel_idx = idx_channel;
325 did_find_channel = true;
326 break;
327 }
328 }
329
330 if (!did_find_channel) {
331 // No channel for this bone. Maybe it's just not animated. Skip it.
332 continue;
333 }
334
335 anim::make_bone_matrices_for_animation_bone(animation_component,
336 ai_animation->mChannels[found_channel_idx],
337 idx_animation, idx_bone);
338 }
339
340 // Finalise bone matrices.
341 // NOTE: Now that we've calculated all the bone matrices for this
342 // animation, we can finalise them.
343 range_named(idx_bone, 0, animation_component->n_bones) {
344 anim::Bone *bone = &animation_component->bones[idx_bone];
345
346 range_named (idx_anim_key, 0, bone->n_anim_keys) {
347 // #slow: We could avoid this multiplication here.
348 m4 *bone_matrix = anim::get_bone_matrix(animation->idx_bone_matrix_set,
349 idx_bone, idx_anim_key);
350
351 *bone_matrix =
352 scene_root_transform *
353 *bone_matrix *
354 bone->offset *
355 inverse_scene_root_transform;
356 }
357 }
358 }
359}
360
361
362void
363models::load_mesh(
364 geom::Mesh *mesh,
365 aiMesh *ai_mesh,
366 const aiScene *scene,
367 ModelLoader *model_loader,
368 m4 transform,
369 pack::Pack indices_pack
370) {
371 mesh->transform = transform;
372 m3 normal_matrix = m3(transpose(inverse(transform)));
373 mesh->mode = GL_TRIANGLES;
374
375 mesh->indices_pack = indices_pack;
376
377 // Vertices
378 if (!ai_mesh->mNormals) {
379 logs::warning("Model does not have normals.");
380 }
381
382 mesh->n_vertices = ai_mesh->mNumVertices;
383 mesh->vertices = (geom::Vertex*)memory::push(&mesh->temp_memory_pool,
384 mesh->n_vertices * sizeof(geom::Vertex), "mesh_vertices");
385
386 for (u32 idx = 0; idx < ai_mesh->mNumVertices; idx++) {
387 geom::Vertex *vertex = &mesh->vertices[idx];
388 *vertex = {};
389
390 v4 raw_vertex_pos = v4(
391 ai_mesh->mVertices[idx].x,
392 ai_mesh->mVertices[idx].y,
393 ai_mesh->mVertices[idx].z,
394 1.0f);
395 vertex->position = v3(mesh->transform * raw_vertex_pos);
396
397 v3 raw_vertex_normal = v3(
398 ai_mesh->mNormals[idx].x,
399 ai_mesh->mNormals[idx].y,
400 ai_mesh->mNormals[idx].z);
401 vertex->normal = normalize(normal_matrix * raw_vertex_normal);
402
403 if (ai_mesh->mTextureCoords[0]) {
404 vertex->tex_coords = v2(ai_mesh->mTextureCoords[0][idx].x, 1 - ai_mesh->mTextureCoords[0][idx].y);
405 }
406 }
407
408 // Indices
409 u32 n_indices = 0;
410 for (u32 idx_face = 0; idx_face < ai_mesh->mNumFaces; idx_face++) {
411 aiFace face = ai_mesh->mFaces[idx_face];
412 n_indices += face.mNumIndices;
413 }
414
415 mesh->n_indices = n_indices;
416 mesh->indices = (u32*)memory::push(&mesh->temp_memory_pool,
417 mesh->n_indices * sizeof(u32), "mesh_indices");
418 u32 idx_index = 0;
419
420 for (u32 idx_face = 0; idx_face < ai_mesh->mNumFaces; idx_face++) {
421 aiFace face = ai_mesh->mFaces[idx_face];
422 for (
423 u32 idx_face_index = 0;
424 idx_face_index < face.mNumIndices;
425 idx_face_index++
426 ) {
427 mesh->indices[idx_index++] = face.mIndices[idx_face_index];
428 }
429 }
430
431 // Bones
432 assert(ai_mesh->mNumBones < MAX_N_BONES);
433 anim::Component *animation_component = &model_loader->animation_component;
434 range_named (idx_bone, 0, ai_mesh->mNumBones) {
435 aiBone *ai_bone = ai_mesh->mBones[idx_bone];
436 u32 idx_found_bone = 0;
437 bool did_find_bone = false;
438
439 range_named (idx_animcomp_bone, 0, animation_component->n_bones) {
440 if (pstr_eq(
441 animation_component->bones[idx_animcomp_bone].name, ai_bone->mName.C_Str()
442 )) {
443 did_find_bone = true;
444 idx_found_bone = idx_animcomp_bone;
445 break;
446 }
447 }
448
449 assert(did_find_bone);
450
451 // NOTE: We really only need to do this once, but I honestly can't be
452 // bothered to add some mechanism to check if we already set it, it would
453 // just make things more complicated. We set it multiple times, whatever.
454 // It's the same value anyway.
455 animation_component->bones[idx_found_bone].offset =
456 util::aimatrix4x4_to_glm(&ai_bone->mOffsetMatrix);
457
458 range_named (idx_weight, 0, ai_bone->mNumWeights) {
459 u32 vertex_idx = ai_bone->mWeights[idx_weight].mVertexId;
460 f32 weight = ai_bone->mWeights[idx_weight].mWeight;
461 assert(vertex_idx < mesh->n_vertices);
462 range_named (idx_vertex_weight, 0, MAX_N_BONES_PER_VERTEX) {
463 // Put it in the next free space, if there is any.
464 if (mesh->vertices[vertex_idx].bone_weights[idx_vertex_weight] == 0) {
465 mesh->vertices[vertex_idx].bone_idxs[idx_vertex_weight] = idx_found_bone;
466 mesh->vertices[vertex_idx].bone_weights[idx_vertex_weight] = weight;
467 break;
468 }
469 }
470 }
471 }
472}
473
474
475void
476models::load_node(
477 ModelLoader *model_loader,
478 aiNode *node, const aiScene *scene,
479 m4 accumulated_transform, pack::Pack indices_pack
480) {
481 m4 node_transform = util::aimatrix4x4_to_glm(&node->mTransformation);
482 m4 transform = accumulated_transform * node_transform;
483
484 range (0, node->mNumMeshes) {
485 aiMesh *ai_mesh = scene->mMeshes[node->mMeshes[idx]];
486 geom::Mesh *mesh = &model_loader->meshes[model_loader->n_meshes++];
487 *mesh = {};
488 load_mesh(mesh, ai_mesh, scene, model_loader, transform, indices_pack);
489 }
490
491 range (0, node->mNumChildren) {
492 pack::Pack new_indices_pack = indices_pack;
493 // NOTE: We can only store 4 bits per pack element. Our indices can be way
494 // bigger than that, but that's fine. We don't need that much precision.
495 // Just smash the number down to a u8.
496 pack::push(&new_indices_pack, (u8)idx);
497 load_node(model_loader, node->mChildren[idx], scene, transform, new_indices_pack);
498 }
499}
500
501
502void
503models::load_model_from_file(ModelLoader *model_loader)
504{
505 // NOTE: This function stores its vertex data in the memory::Pool for each
506 // mesh, and so is intended to be called from a separate thread.
507 char full_path[MAX_PATH] = {};
508 pstr_vcat(full_path, MAX_PATH, MODEL_DIR, model_loader->model_path, NULL);
509
510 START_TIMER(assimp_import);
511 auto flags =
512 aiProcess_Triangulate
513 | aiProcess_JoinIdenticalVertices
514 | aiProcess_SortByPType
515 | aiProcess_GenNormals
516 | aiProcess_FlipUVs
517 // NOTE: This might break something in the future, let's look out for it.
518 | aiProcess_OptimizeMeshes
519 // NOTE: Use with caution, goes full YOLO.
520 /* aiProcess_OptimizeGraph */
521 /* | aiProcess_CalcTangentSpace */
522 ;
523 const aiScene *scene = aiImportFile(full_path, flags);
524 END_TIMER(assimp_import);
525
526 if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
527 logs::fatal("assimp error: %s", aiGetErrorString());
528 return;
529 }
530
531 anim::Component *animation_component = &model_loader->animation_component;
532 load_bones(animation_component, scene);
533 load_node(model_loader, scene->mRootNode, scene, m4(1.0f), 0ULL);
534 load_animations(animation_component, scene);
535 aiReleaseImport(scene);
536
537 model_loader->state = ModelLoaderState::mesh_data_loaded;
538}
539
540
541void
542models::load_model_from_data(ModelLoader *model_loader)
543{
544 // NOTE: This function sets up mesh vertex buffers directly, and so is
545 // intended to be called from the main OpenGL thread.
546 memory::Pool temp_memory_pool = {};
547
548 geom::Vertex *vertex_data = nullptr;
549 u32 n_vertices = 0;
550 u32 *index_data = nullptr;
551 u32 n_indices = 0;
552 GLenum mode = 0;
553
554 if (pstr_eq(model_loader->model_path, "builtin:axes")) {
555 vertex_data = (geom::Vertex*)AXES_VERTICES;
556 n_vertices = 6;
557 index_data = nullptr;
558 n_indices = 0;
559 mode = GL_LINES;
560 } else if (pstr_eq(model_loader->model_path, "builtin:ocean")) {
561 geom::make_plane(&temp_memory_pool,
562 200, 200, 800, 800, &n_vertices, &n_indices, &vertex_data, &index_data);
563 mode = GL_TRIANGLES;
564 } else if (pstr_eq(model_loader->model_path, "builtin:skysphere")) {
565 geom::make_sphere(&temp_memory_pool,
566 64, 64, &n_vertices, &n_indices, &vertex_data, &index_data);
567 mode = GL_TRIANGLE_STRIP;
568 } else if (
569 pstr_starts_with(model_loader->model_path, "builtin:screenquad")
570 ) {
571 vertex_data = (geom::Vertex*)SCREENQUAD_VERTICES;
572 n_vertices = 6;
573 index_data = nullptr;
574 n_indices = 0;
575 mode = GL_TRIANGLES;
576 } else {
577 logs::fatal("Could not find builtin model: %s", model_loader->model_path);
578 }
579
580 geom::Mesh *mesh = &model_loader->meshes[model_loader->n_meshes++];
581 *mesh = {};
582 mesh->transform = m4(1.0f);
583 mesh->mode = mode;
584 mesh->n_vertices = n_vertices;
585 mesh->n_indices = n_indices;
586 mesh->indices_pack = 0UL;
587
588 geom::setup_mesh_vertex_buffers(mesh, vertex_data, n_vertices, index_data, n_indices);
589 model_loader->state = ModelLoaderState::vertex_buffers_set_up;
590
591 memory::destroy_memory_pool(&temp_memory_pool);
592}