PixelBullet  0.0.1
A C++ game engine
Loading...
Searching...
No Matches
gltf_import_shared_internal.h
1#pragma once
2
3#include "morph_mesh_internal.h"
4#include "pixelbullet/application/log.h"
5#include "pixelbullet/assets/bitmap.h"
6#include "pixelbullet/assets/gltf_prefab_import.h"
7#include "pixelbullet/assets/material.h"
8#include "pixelbullet/filesystem/filesystem.h"
9#include "pixelbullet/scene/components/transform.h"
10#include "pixelbullet/scene/prefab_asset.h"
11#include "skinned_mesh_internal.h"
12#include "skinned_morph_mesh_internal.h"
13#include "static_mesh_internal.h"
14
15#include "cgltf.h"
16
17#include <glm/gtc/matrix_transform.hpp>
18#include <glm/gtx/quaternion.hpp>
19
20#include <algorithm>
21#include <cctype>
22#include <cstring>
23#include <filesystem>
24#include <fmt/format.h>
25#include <memory>
26#include <optional>
27#include <string_view>
28#include <unordered_map>
29#include <unordered_set>
30#include <utility>
31#include <vector>
32
33namespace pixelbullet::gltf_import_internal
34{
35inline std::shared_ptr<log::Logger> GetAssetsLogger()
36{
37 return logging::Get(logging::names::core_assets);
38}
39
40constexpr std::string_view k_assets_prefix = "@assets/";
41constexpr std::string_view k_shared_prefix = "@shared/";
42
44{
45 std::string logical_root;
46 std::filesystem::path physical_root;
47 std::filesystem::path relative_source_path;
48 std::filesystem::path relative_source_stem;
49};
50
52{
53 PrefabAsset prefab;
54 VirtualPath prefab_path;
55 std::vector<std::pair<VirtualPath, static_mesh_internal::LoadData>> meshes;
56 std::vector<std::pair<VirtualPath, morph_mesh_internal::LoadData>> morph_meshes;
57 std::vector<std::pair<VirtualPath, skinned_mesh_internal::LoadData>> skinned_meshes;
58 std::vector<std::pair<VirtualPath, skinned_morph_mesh_internal::LoadData>> skinned_morph_meshes;
59 std::vector<std::pair<VirtualPath, Material>> materials;
60 std::vector<std::pair<VirtualPath, Bitmap>> textures;
61};
62
64{
65 const Filesystem* filesystem = nullptr;
66 VirtualPath source_path;
67 std::filesystem::path resolved_source_path;
68 SourceRoot source_root;
69 cgltf_data* gltf = nullptr;
70 GeneratedFiles generated;
71 std::unordered_map<const cgltf_image*, VirtualPath> image_paths;
72 std::unordered_map<const cgltf_material*, VirtualPath> material_paths;
73 std::unordered_map<const cgltf_primitive*, VirtualPath> primitive_mesh_paths;
74 std::unordered_map<const cgltf_node*, EntityId> imported_node_entities;
75 std::optional<VirtualPath> default_material_path;
76 std::unordered_map<std::string, std::size_t> mesh_name_counts;
77 std::unordered_map<std::string, std::size_t> material_name_counts;
78 std::unordered_map<std::string, std::size_t> texture_name_counts;
79 std::vector<std::string> warning_messages;
80 std::unordered_set<std::string> seen_warning_messages;
81 std::string failure_message;
82};
83
84inline bool StartsWith(const std::string_view value, const std::string_view prefix) noexcept
85{
86 return value.size() >= prefix.size() && value.substr(0, prefix.size()) == prefix;
87}
88
89inline std::string SanitizeStem(const std::string_view value, const std::string_view fallback)
90{
91 std::string sanitized;
92 sanitized.reserve(value.size());
93 for (const char ch : value)
94 {
95 if (std::isalnum(static_cast<unsigned char>(ch)) || ch == '_' || ch == '-')
96 {
97 sanitized.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(ch))));
98 }
99 else if (ch == ' ' || ch == '.' || ch == '/' || ch == '\\')
100 {
101 if (sanitized.empty() || sanitized.back() != '_')
102 {
103 sanitized.push_back('_');
104 }
105 }
106 }
107
108 while (!sanitized.empty() && sanitized.back() == '_')
109 {
110 sanitized.pop_back();
111 }
112
113 if (sanitized.empty())
114 {
115 sanitized = std::string(fallback);
116 }
117
118 return sanitized;
119}
120
121inline std::string MakeUniqueStem(std::unordered_map<std::string, std::size_t>& counts, const std::string_view preferred,
122 const std::string_view fallback)
123{
124 std::string stem = SanitizeStem(preferred.empty() ? fallback : preferred, fallback);
125 const auto [it, inserted] = counts.emplace(stem, 0u);
126 if (inserted)
127 {
128 return stem;
129 }
130
131 ++it->second;
132 return stem + "_" + std::to_string(it->second);
133}
134
135inline void SetFailure(ImportState& state, std::string message)
136{
137 if (state.failure_message.empty())
138 {
139 state.failure_message = std::move(message);
140 }
141}
142
143template <typename... Args>
144inline void RecordWarning(ImportState& state, fmt::format_string<Args...> fmt_str, Args&&... args)
145{
146 std::string message = fmt::format(fmt_str, std::forward<Args>(args)...);
147 if (state.seen_warning_messages.insert(message).second)
148 {
149 state.warning_messages.push_back(std::move(message));
150 GetAssetsLogger()->Warn("{}", state.warning_messages.back());
151 }
152}
153
154inline std::optional<SourceRoot> ResolveSourceRoot(const Filesystem& filesystem, const VirtualPath& source_path)
155{
156 const std::string_view logical_path = source_path.LogicalPath();
157 if (StartsWith(logical_path, k_assets_prefix))
158 {
159 const std::filesystem::path relative = std::filesystem::path(std::string(logical_path.substr(k_assets_prefix.size())));
160 std::filesystem::path stem = relative;
161 stem.replace_extension();
162 return SourceRoot{
163 .logical_root = "@assets",
164 .physical_root = filesystem.ResolveWritable(VirtualPath("@assets")),
165 .relative_source_path = std::move(relative),
166 .relative_source_stem = stem,
167 };
168 }
169
170 if (StartsWith(logical_path, k_shared_prefix))
171 {
172 const std::filesystem::path relative = std::filesystem::path(std::string(logical_path.substr(k_shared_prefix.size())));
173 std::filesystem::path stem = relative;
174 stem.replace_extension();
175 return SourceRoot{
176 .logical_root = "@shared",
177 .physical_root = filesystem.ResolveWritable(VirtualPath("@shared")),
178 .relative_source_path = std::move(relative),
179 .relative_source_stem = stem,
180 };
181 }
182
183 return std::nullopt;
184}
185
186inline VirtualPath BuildGeneratedPath(const ImportState& state, const std::string_view type_root,
187 const std::filesystem::path& relative_path)
188{
189 std::string logical = state.source_root.logical_root;
190 logical.push_back('/');
191 logical.append(type_root);
192 logical.push_back('/');
193 logical.append(relative_path.generic_string());
194 return VirtualPath(std::move(logical));
195}
196
197inline std::filesystem::path BuildImportedNamespace(const ImportState& state)
198{
199 return std::filesystem::path("imported") / state.source_root.relative_source_stem;
200}
201
202inline VirtualPath BuildGeneratedPrefabPath(const ImportState& state)
203{
204 std::filesystem::path relative = BuildImportedNamespace(state);
205 relative += ".prefab.yaml";
206 return BuildGeneratedPath(state, "prefabs", relative);
207}
208
209inline VirtualPath BuildGeneratedMeshPath(const ImportState& state, const std::string_view stem)
210{
211 return BuildGeneratedPath(state, "models", BuildImportedNamespace(state) / (std::string(stem) + ".mesh.bin"));
212}
213
214inline VirtualPath BuildGeneratedSkinnedMeshPath(const ImportState& state, const std::string_view stem)
215{
216 return BuildGeneratedPath(state, "models", BuildImportedNamespace(state) / (std::string(stem) + ".skinned_mesh.bin"));
217}
218
219inline VirtualPath BuildGeneratedMorphMeshPath(const ImportState& state, const std::string_view stem)
220{
221 return BuildGeneratedPath(state, "models", BuildImportedNamespace(state) / (std::string(stem) + ".morph_mesh.bin"));
222}
223
224inline VirtualPath BuildGeneratedSkinnedMorphMeshPath(const ImportState& state, const std::string_view stem)
225{
226 return BuildGeneratedPath(state, "models", BuildImportedNamespace(state) / (std::string(stem) + ".skinned_morph_mesh.bin"));
227}
228
229inline VirtualPath BuildGeneratedMaterialPath(const ImportState& state, const std::string_view stem)
230{
231 return BuildGeneratedPath(state, "materials", BuildImportedNamespace(state) / (std::string(stem) + ".material.yaml"));
232}
233
234inline VirtualPath BuildGeneratedTexturePath(const ImportState& state, const std::string_view stem)
235{
236 return BuildGeneratedPath(state, "textures", BuildImportedNamespace(state) / (std::string(stem) + ".png"));
237}
238
239inline glm::mat4 BuildNodeLocalMatrix(const cgltf_node& node)
240{
241 if (node.has_matrix)
242 {
243 glm::mat4 matrix(1.0f);
244 std::memcpy(&matrix[0][0], node.matrix, sizeof(node.matrix));
245 return matrix;
246 }
247
248 const glm::vec3 translation =
249 node.has_translation ? glm::vec3(node.translation[0], node.translation[1], node.translation[2]) : glm::vec3(0.0f);
250 const glm::quat rotation =
251 node.has_rotation ? glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]) : glm::quat(glm::vec3(0.0f));
252 const glm::vec3 scale = node.has_scale ? glm::vec3(node.scale[0], node.scale[1], node.scale[2]) : glm::vec3(1.0f);
253 return glm::translate(glm::mat4(1.0f), translation) * glm::toMat4(rotation) * glm::scale(glm::mat4(1.0f), scale);
254}
255
256inline Transform BuildNodeTransform(const cgltf_node& node)
257{
258 Transform transform;
259 if (!TryDecomposeTransform(BuildNodeLocalMatrix(node), transform))
260 {
261 return Transform{};
262 }
263 return transform;
264}
265
266inline bool ReadFloatAccessorElement(const cgltf_accessor& accessor, const cgltf_size index, const cgltf_size expected_components,
267 float* values)
268{
269 std::fill(values, values + expected_components, 0.0f);
270 return cgltf_accessor_read_float(&accessor, index, values, expected_components) != 0;
271}
272
273inline bool ReadUintAccessorElement(const cgltf_accessor& accessor, const cgltf_size index, const cgltf_size expected_components,
274 uint32_t* values)
275{
276 std::fill(values, values + expected_components, 0u);
277 return cgltf_accessor_read_uint(&accessor, index, values, expected_components) != 0;
278}
279
280inline std::size_t FindMaterialIndex(const ImportState& state, const cgltf_material& material) noexcept
281{
282 return static_cast<std::size_t>(&material - state.gltf->materials);
283}
284
285inline std::size_t FindImageIndex(const ImportState& state, const cgltf_image& image) noexcept
286{
287 return static_cast<std::size_t>(&image - state.gltf->images);
288}
289
290inline std::size_t FindMeshIndex(const ImportState& state, const cgltf_mesh& mesh) noexcept
291{
292 return static_cast<std::size_t>(&mesh - state.gltf->meshes);
293}
294
295inline std::size_t FindNodeIndex(const ImportState& state, const cgltf_node& node) noexcept
296{
297 return static_cast<std::size_t>(&node - state.gltf->nodes);
298}
299
300inline std::optional<uint32_t> ResolveTextureCoordinateIndex(const cgltf_texture_view& view, std::string* error_message)
301{
302 if (error_message)
303 {
304 error_message->clear();
305 }
306
307 if (view.texture == nullptr)
308 {
309 return 0u;
310 }
311
312 const cgltf_int texcoord = view.has_transform && view.transform.has_texcoord ? view.transform.texcoord : view.texcoord;
313 if (texcoord == 0)
314 {
315 return 0u;
316 }
317 if (texcoord == 1)
318 {
319 return 1u;
320 }
321
322 if (error_message)
323 {
324 *error_message = "glTF textures that reference TEXCOORD_2+ are not supported.";
325 }
326 return std::nullopt;
327}
328
329inline MaterialTextureUvSet ResolveTextureUvSet(const cgltf_texture_view& view, std::string* error_message)
330{
331 const std::optional<uint32_t> texcoord_index = ResolveTextureCoordinateIndex(view, error_message);
332 if (!texcoord_index.has_value())
333 {
334 return MaterialTextureUvSet::Uv0;
335 }
336
337 return *texcoord_index == 1u ? MaterialTextureUvSet::Uv1 : MaterialTextureUvSet::Uv0;
338}
339
340inline MaterialTextureTransform ResolveTextureUvTransform(const cgltf_texture_view& view) noexcept
341{
342 MaterialTextureTransform transform;
343 if (!view.has_transform)
344 {
345 return transform;
346 }
347
348 transform.offset = glm::vec2(view.transform.offset[0], view.transform.offset[1]);
349 transform.scale = glm::vec2(view.transform.scale[0], view.transform.scale[1]);
350 transform.rotation_degrees = glm::degrees(view.transform.rotation);
351 return transform;
352}
353
354[[nodiscard]] std::optional<VirtualPath> GetOrCreateMaterialPath(ImportState& state, const cgltf_material* material);
355[[nodiscard]] std::optional<VirtualPath> GetOrCreatePrimitiveMeshPath(ImportState& state, const cgltf_mesh& mesh,
356 const cgltf_primitive& primitive, std::size_t primitive_index);
357[[nodiscard]] std::optional<VirtualPath> GetOrCreatePrimitiveSkinnedMeshPath(ImportState& state, const cgltf_mesh& mesh,
358 const cgltf_primitive& primitive, const cgltf_skin& skin,
359 std::size_t primitive_index);
360[[nodiscard]] std::optional<VirtualPath> GetOrCreatePrimitiveMorphMeshPath(ImportState& state, const cgltf_mesh& mesh,
361 const cgltf_primitive& primitive, std::size_t primitive_index);
362[[nodiscard]] std::optional<VirtualPath> GetOrCreatePrimitiveSkinnedMorphMeshPath(ImportState& state, const cgltf_mesh& mesh,
363 const cgltf_primitive& primitive, const cgltf_skin& skin,
364 std::size_t primitive_index);
365[[nodiscard]] std::optional<std::vector<float>> ResolveNodeMorphWeights(const cgltf_node& node, const cgltf_mesh& mesh,
366 std::string* error_message);
367bool ImportNodeAnimations(ImportState& state, PrefabAsset& prefab, std::string* error_message);
368bool BuildImportedPrefab(ImportState& state, std::string* error_message);
369bool CommitGeneratedFiles(ImportState& state, GltfPrefabImportResult& result);
370} // namespace pixelbullet::gltf_import_internal
Definition filesystem.h:16
Definition virtual_path.h:10
Definition prefab_asset.h:9
Definition gltf_import_shared_internal.h:52
Definition gltf_import_shared_internal.h:64
Definition gltf_import_shared_internal.h:44