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"
17#include <glm/gtc/matrix_transform.hpp>
18#include <glm/gtx/quaternion.hpp>
24#include <fmt/format.h>
28#include <unordered_map>
29#include <unordered_set>
33namespace pixelbullet::gltf_import_internal
35inline std::shared_ptr<log::Logger> GetAssetsLogger()
37 return logging::Get(logging::names::core_assets);
40constexpr std::string_view k_assets_prefix =
"@assets/";
41constexpr std::string_view k_shared_prefix =
"@shared/";
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;
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;
67 std::filesystem::path resolved_source_path;
69 cgltf_data* gltf =
nullptr;
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;
84inline bool StartsWith(
const std::string_view value,
const std::string_view prefix)
noexcept
86 return value.size() >= prefix.size() && value.substr(0, prefix.size()) == prefix;
89inline std::string SanitizeStem(
const std::string_view value,
const std::string_view fallback)
91 std::string sanitized;
92 sanitized.reserve(value.size());
93 for (
const char ch : value)
95 if (std::isalnum(
static_cast<unsigned char>(ch)) || ch ==
'_' || ch ==
'-')
97 sanitized.push_back(
static_cast<char>(std::tolower(
static_cast<unsigned char>(ch))));
99 else if (ch ==
' ' || ch ==
'.' || ch ==
'/' || ch ==
'\\')
101 if (sanitized.empty() || sanitized.back() !=
'_')
103 sanitized.push_back(
'_');
108 while (!sanitized.empty() && sanitized.back() ==
'_')
110 sanitized.pop_back();
113 if (sanitized.empty())
115 sanitized = std::string(fallback);
121inline std::string MakeUniqueStem(std::unordered_map<std::string, std::size_t>& counts,
const std::string_view preferred,
122 const std::string_view fallback)
124 std::string stem = SanitizeStem(preferred.empty() ? fallback : preferred, fallback);
125 const auto [it, inserted] = counts.emplace(stem, 0u);
132 return stem +
"_" + std::to_string(it->second);
135inline void SetFailure(
ImportState& state, std::string message)
137 if (state.failure_message.empty())
139 state.failure_message = std::move(message);
143template <
typename... Args>
144inline void RecordWarning(
ImportState& state, fmt::format_string<Args...> fmt_str, Args&&... args)
146 std::string message = fmt::format(fmt_str, std::forward<Args>(args)...);
147 if (state.seen_warning_messages.insert(message).second)
149 state.warning_messages.push_back(std::move(message));
150 GetAssetsLogger()->Warn(
"{}", state.warning_messages.back());
154inline std::optional<SourceRoot> ResolveSourceRoot(
const Filesystem& filesystem,
const VirtualPath& source_path)
156 const std::string_view logical_path = source_path.LogicalPath();
157 if (StartsWith(logical_path, k_assets_prefix))
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();
163 .logical_root =
"@assets",
164 .physical_root = filesystem.ResolveWritable(VirtualPath(
"@assets")),
165 .relative_source_path = std::move(relative),
166 .relative_source_stem = stem,
170 if (StartsWith(logical_path, k_shared_prefix))
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();
176 .logical_root =
"@shared",
177 .physical_root = filesystem.ResolveWritable(VirtualPath(
"@shared")),
178 .relative_source_path = std::move(relative),
179 .relative_source_stem = stem,
186inline VirtualPath BuildGeneratedPath(
const ImportState& state,
const std::string_view type_root,
187 const std::filesystem::path& relative_path)
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));
197inline std::filesystem::path BuildImportedNamespace(
const ImportState& state)
199 return std::filesystem::path(
"imported") / state.source_root.relative_source_stem;
202inline VirtualPath BuildGeneratedPrefabPath(
const ImportState& state)
204 std::filesystem::path relative = BuildImportedNamespace(state);
205 relative +=
".prefab.yaml";
206 return BuildGeneratedPath(state,
"prefabs", relative);
209inline VirtualPath BuildGeneratedMeshPath(
const ImportState& state,
const std::string_view stem)
211 return BuildGeneratedPath(state,
"models", BuildImportedNamespace(state) / (std::string(stem) +
".mesh.bin"));
214inline VirtualPath BuildGeneratedSkinnedMeshPath(
const ImportState& state,
const std::string_view stem)
216 return BuildGeneratedPath(state,
"models", BuildImportedNamespace(state) / (std::string(stem) +
".skinned_mesh.bin"));
219inline VirtualPath BuildGeneratedMorphMeshPath(
const ImportState& state,
const std::string_view stem)
221 return BuildGeneratedPath(state,
"models", BuildImportedNamespace(state) / (std::string(stem) +
".morph_mesh.bin"));
224inline VirtualPath BuildGeneratedSkinnedMorphMeshPath(
const ImportState& state,
const std::string_view stem)
226 return BuildGeneratedPath(state,
"models", BuildImportedNamespace(state) / (std::string(stem) +
".skinned_morph_mesh.bin"));
229inline VirtualPath BuildGeneratedMaterialPath(
const ImportState& state,
const std::string_view stem)
231 return BuildGeneratedPath(state,
"materials", BuildImportedNamespace(state) / (std::string(stem) +
".material.yaml"));
234inline VirtualPath BuildGeneratedTexturePath(
const ImportState& state,
const std::string_view stem)
236 return BuildGeneratedPath(state,
"textures", BuildImportedNamespace(state) / (std::string(stem) +
".png"));
239inline glm::mat4 BuildNodeLocalMatrix(
const cgltf_node& node)
243 glm::mat4 matrix(1.0f);
244 std::memcpy(&matrix[0][0], node.matrix,
sizeof(node.matrix));
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);
256inline Transform BuildNodeTransform(
const cgltf_node& node)
259 if (!TryDecomposeTransform(BuildNodeLocalMatrix(node), transform))
266inline bool ReadFloatAccessorElement(
const cgltf_accessor& accessor,
const cgltf_size index,
const cgltf_size expected_components,
269 std::fill(values, values + expected_components, 0.0f);
270 return cgltf_accessor_read_float(&accessor, index, values, expected_components) != 0;
273inline bool ReadUintAccessorElement(
const cgltf_accessor& accessor,
const cgltf_size index,
const cgltf_size expected_components,
276 std::fill(values, values + expected_components, 0u);
277 return cgltf_accessor_read_uint(&accessor, index, values, expected_components) != 0;
280inline std::size_t FindMaterialIndex(
const ImportState& state,
const cgltf_material& material)
noexcept
282 return static_cast<std::size_t
>(&material - state.gltf->materials);
285inline std::size_t FindImageIndex(
const ImportState& state,
const cgltf_image& image)
noexcept
287 return static_cast<std::size_t
>(&image - state.gltf->images);
290inline std::size_t FindMeshIndex(
const ImportState& state,
const cgltf_mesh& mesh)
noexcept
292 return static_cast<std::size_t
>(&mesh - state.gltf->meshes);
295inline std::size_t FindNodeIndex(
const ImportState& state,
const cgltf_node& node)
noexcept
297 return static_cast<std::size_t
>(&node - state.gltf->nodes);
300inline std::optional<uint32_t> ResolveTextureCoordinateIndex(
const cgltf_texture_view& view, std::string* error_message)
304 error_message->clear();
307 if (view.texture ==
nullptr)
312 const cgltf_int texcoord = view.has_transform && view.transform.has_texcoord ? view.transform.texcoord : view.texcoord;
324 *error_message =
"glTF textures that reference TEXCOORD_2+ are not supported.";
329inline MaterialTextureUvSet ResolveTextureUvSet(
const cgltf_texture_view& view, std::string* error_message)
331 const std::optional<uint32_t> texcoord_index = ResolveTextureCoordinateIndex(view, error_message);
332 if (!texcoord_index.has_value())
334 return MaterialTextureUvSet::Uv0;
337 return *texcoord_index == 1u ? MaterialTextureUvSet::Uv1 : MaterialTextureUvSet::Uv0;
340inline MaterialTextureTransform ResolveTextureUvTransform(
const cgltf_texture_view& view)
noexcept
342 MaterialTextureTransform transform;
343 if (!view.has_transform)
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);
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);
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