SleakEngine 0.1.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
ModelLoader.hpp
Go to the documentation of this file.
1#ifndef _MODELLOADER_HPP_
2#define _MODELLOADER_HPP_
3
4#include <Core/OSDef.hpp>
5#include <Math/Matrix.hpp>
6#include <Math/Quaternion.hpp>
7#include <Math/Vector.hpp>
8#include <cstdint>
9#include <string>
10#include <unordered_map>
11#include <vector>
12
13struct aiNode;
14struct aiScene;
15struct aiMesh;
16struct aiMaterial;
17struct aiAnimation;
18
19namespace Sleak {
20
21 class GameObject;
22 class Material;
23 class Texture;
24 class Skeleton;
25 class AnimationClip;
26 struct MeshData;
27 template <typename T> class RefPtr;
28
29 /// Import-time adjustments applied while loading a model file.
30 /// @ingroup rendering
32 float scaleFactor = 1.0f;
33 bool flipUVs = true;
34 bool flipNormals = false;
35 bool flipWinding = false;
38 };
39
40 /// Per-load texture cache to avoid loading the same texture file multiple times.
42 std::unordered_map<std::string, RefPtr<::Sleak::Texture>>;
43
44 /// Per-load cache so meshes sharing an Assimp material share one Material.
45 using MaterialCache = std::unordered_map<uint64_t, RefPtr<Material>>;
46
47 /// Assimp-backed importer that builds a GameObject hierarchy (meshes, materials, optionally a skeleton) from a model file.
48 ///
49 /// Load() reads any format Assimp supports (FBX, glTF, OBJ, and the
50 /// rest) and returns the root of a ready-to-use GameObject tree with
51 /// meshes, materials, and textures attached. Rigged files also get a
52 /// Skeleton and an AnimatorComponent. The caller owns the returned
53 /// root: hand it to SceneBase::AddObject() and the scene takes over.
54 /// Load() returns nullptr on failure, so always check.
55 ///
56 /// ModelLoadOptions covers the adjustments every import pipeline
57 /// eventually needs. `scaleFactor` converts source units to meters
58 /// (0.01 for a centimeter-based rig). `flipUVs` defaults to true
59 /// because most exporters disagree with the engine's texture origin.
60 /// `flipNormals` and `flipWinding` fix models that import inside out or
61 /// with black faces, which is nearly always an exporter handedness
62 /// mismatch rather than a shading problem.
63 ///
64 /// When your mesh and your animations ship as separate files, load the
65 /// mesh once and pull the clips in with LoadAnimationsOnly(), passing
66 /// the skeleton the mesh import produced. Textures are cached for the
67 /// duration of a single Load(), so a model reusing one texture across
68 /// many materials reads it from disk once.
69 ///
70 /// @code{.cpp}
71 /// Sleak::ModelLoadOptions opts;
72 /// opts.scaleFactor = 0.01f; // source is in centimeters
73 /// opts.flipUVs = true;
74 /// opts.position = Sleak::Math::Vector3D(0.0f, 0.0f, 0.0f);
75 ///
76 /// Sleak::GameObject* model =
77 /// Sleak::ModelLoader::Load("assets/models/Mannequin.fbx", opts);
78 /// if (!model) {
79 /// SLEAK_ERROR("Failed to load mannequin");
80 /// return;
81 /// }
82 /// AddObject(model);
83 ///
84 /// // Attach clips from separate files to the imported skeleton
85 /// if (auto* anim = model->GetComponent<Sleak::AnimatorComponent>()) {
86 /// for (auto* clip : Sleak::ModelLoader::LoadAnimationsOnly(
87 /// "assets/animations/Walking.fbx", anim->GetSkeleton())) {
88 /// if (!clip) continue;
89 /// clip->name = "Walking";
90 /// anim->AddClip(clip);
91 /// }
92 /// anim->Play("Walking", true);
93 /// }
94 /// @endcode
95 ///
96 /// @see ModelLoadOptions, GameObject, AnimatorComponent, Skeleton,
97 /// AnimationClip, Material
98 /// @ingroup rendering
99 class ENGINE_API ModelLoader {
100 public:
101 /// Imports a model file and returns the root of the resulting GameObject hierarchy.
102 static GameObject* Load(const std::string& filePath,
103 const ModelLoadOptions& options = {});
104
105 /// Load only animations from an FBX, reusing an existing skeleton.
106 static std::vector<AnimationClip*> LoadAnimationsOnly(
107 const std::string& filePath, Skeleton* skeleton);
108
109 private:
110 /// Static mesh path (with PreTransformVertices).
111 static void ProcessNode(aiNode* node, const aiScene* scene,
112 GameObject* parent, const std::string& directory,
113 const ModelLoadOptions& options,
114 TextureCache& textureCache,
115 MaterialCache& materialCache);
116
117 /// Animated mesh path (without PreTransformVertices).
118 static void ProcessNodeAnimated(
119 aiNode* node, const aiScene* scene, GameObject* parent,
120 const std::string& directory, const ModelLoadOptions& options,
121 TextureCache& textureCache, MaterialCache& materialCache,
122 Skeleton* skeleton, std::vector<AnimationClip*>& clips);
123
124 /// Converts one Assimp mesh into engine MeshData, wiring up bone weights
125 /// when a skeleton is given.
126 static MeshData ProcessMesh(aiMesh* mesh, const ModelLoadOptions& options,
127 Skeleton* skeleton = nullptr);
128
129 /// Converts one Assimp material into an engine Material, loading its
130 /// textures through textureCache.
131 static RefPtr<Material> ProcessMaterial(aiMaterial* mat,
132 const aiScene* scene,
133 const std::string& directory,
134 TextureCache& textureCache,
135 bool skinned = false);
136
137 /// Resolves and loads a single texture slot off an Assimp material,
138 /// reusing textureCache when possible.
139 static RefPtr<::Sleak::Texture> LoadMaterialTexture(
140 aiMaterial* mat, int type, const aiScene* scene,
141 const std::string& directory, TextureCache& textureCache);
142
143 /// Builds a Skeleton from the scene's bone hierarchy.
144 static Skeleton* ExtractSkeleton(const aiScene* scene);
145 /// Converts every Assimp animation in the scene into engine AnimationClips
146 /// bound to skeleton.
147 static std::vector<AnimationClip*> ExtractAnimations(const aiScene* scene,
148 Skeleton* skeleton);
149 /// Recursively registers each Assimp node as a skeleton bone under
150 /// parentId.
151 static void BuildBoneHierarchy(const aiNode* node, Skeleton* skeleton,
152 int parentId);
153 /// Recursively mirrors the Assimp node tree into the skeleton's bone tree,
154 /// returning the created bone's id.
155 static int BuildNodeTree(const aiNode* node, Skeleton* skeleton);
156
157 /// Assimp matrix to engine matrix conversion.
158 static Math::Matrix4 ConvertMatrix(const void* aiMat);
159 };
160
161} // namespace Sleak
162
163#endif // _MODELLOADER_HPP_
Represents a quaternion for 3D rotations.
static GameObject * Load(const std::string &filePath, const ModelLoadOptions &options={})
Imports a model file and returns the root of the resulting GameObject hierarchy.
static std::vector< AnimationClip * > LoadAnimationsOnly(const std::string &filePath, Skeleton *skeleton)
Load only animations from an FBX, reusing an existing skeleton.
Matrix< float, 4, 4 > Matrix4
Definition Matrix.hpp:413
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
std::unordered_map< std::string, RefPtr<::Sleak::Texture > > TextureCache
Per-load texture cache to avoid loading the same texture file multiple times.
std::unordered_map< uint64_t, RefPtr< Material > > MaterialCache
Per-load cache so meshes sharing an Assimp material share one Material.
Math::Vector3D position
Math::Quaternion rotation