Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
GltfLoader.cpp
Go to the documentation of this file.
6#include <future>
7#include <plog/Log.h>
8#include <sstream>
13#ifdef ENABLE_TRACY
14#include <tracy/TracyC.h>
15#include <tracy/Tracy.hpp>
17#endif
18namespace EngineCore {
21
23 if (threadPool) {
24 threadPool->wait();
25 }
26 }
27
28 GltfLoader::GltfBufferDataView::GltfBufferDataView(const std::vector<tinygltf::Buffer>& buffers, const std::vector<tinygltf::BufferView>& bufferViews, const tinygltf::Accessor& accessor)
30 data = reinterpret_cast<const float *>(&buffer.data[bufferView.byteOffset + accessor.byteOffset]);
31 }
32
33 GltfLoader::GltfVertexData::GltfVertexData(const std::vector<tinygltf::Buffer> &buffers,
34 const std::vector<tinygltf::BufferView> &bufferViews,
35 const std::vector<tinygltf::Accessor>& accessors,
36 const tinygltf::Primitive &primitive) {
37#define EXTRACT_VERTEX_DATA( view, attribKey ) \
38 { \
39 constexpr const char * positionKey = attribKey; \
40 if ( primitive.attributes.find( attribKey ) != primitive.attributes.end() ) \
41 { \
42 view.emplace( buffers, bufferViews, accessors.at(primitive.attributes.at( attribKey ))); \
43 } \
44 }
45
48 EXTRACT_VERTEX_DATA( tangentDataView, "TANGENT" ) // vec4: xyz=tangent, w=handedness
49 EXTRACT_VERTEX_DATA( uvDataView, "TEXCOORD_0" )
51
52 indexDataView.emplace(GltfBufferDataView( buffers, bufferViews, accessors[primitive.indices] ));
53
54
55#undef EXTRACT_VERTEX_DATA
56 }
57
58 std::vector<Vertex> GltfLoader::getPrimitiveVertices(const tinygltf::Primitive &primitive) {
59 throw std::logic_error("Not implemented");
60 }
61
63 TRACY_ZONE_SCOPED_NAMED("Process Primitive");
64 assert(vertexData.indexDataView.has_value());
65 assert(vertexData.indexDataView->data != nullptr);
66 assert(vertexData.indexDataView->accessor.count > 0);
67
68 Vertex vertex;
69 std::vector<Vertex> vertices;
70 std::vector<uint32_t> indices;
71 vertices.reserve( vertexData.indexDataView->accessor.count );
72 indices.reserve( vertexData.indexDataView->accessor.count );
73 for (uint32_t index = 0; index < vertexData.indexDataView->accessor.count; index++)
74 {
75 uint32_t vertexIndex = 0;
76 switch (vertexData.indexDataView->accessor.componentType) {
77 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
78 vertexIndex = static_cast<uint32_t>(reinterpret_cast<const uint8_t *>(vertexData.indexDataView->data)[index]);
79 break;
80 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
81 vertexIndex = static_cast<uint32_t>(reinterpret_cast<const uint16_t *>(vertexData.indexDataView->data)[index]);
82 break;
83 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
84 vertexIndex = reinterpret_cast<const uint32_t *>( vertexData.indexDataView->data )[index];
85 break;
86 default:
87 PLOGF << "Unsupported index type! Type: "
88 << vertexData.indexDataView->accessor.componentType;
89 }
90
91 if ( vertexData.positionDataView.has_value() )
92 {
93 vertex.position = glm::vec4( vertexData.positionDataView->data[3 * vertexIndex + 0],
94 vertexData.positionDataView->data[3 * vertexIndex + 1],
95 vertexData.positionDataView->data[3 * vertexIndex + 2],
96 0.0f );
97 }
98
99 if ( vertexData.normalDataView.has_value() )
100 {
101 vertex.normal = glm::vec4( vertexData.normalDataView->data[3 * vertexIndex + 0],
102 vertexData.normalDataView->data[3 * vertexIndex + 1],
103 vertexData.normalDataView->data[3 * vertexIndex + 2],
104 0.0f );
105 }
106
107 // Extract tangent (vec4: xyz=tangent direction, w=handedness)
108 if ( vertexData.tangentDataView.has_value() )
109 {
110 vertex.tangent = glm::vec4( vertexData.tangentDataView->data[4 * vertexIndex + 0],
111 vertexData.tangentDataView->data[4 * vertexIndex + 1],
112 vertexData.tangentDataView->data[4 * vertexIndex + 2],
113 vertexData.tangentDataView->data[4 * vertexIndex + 3] ); // w = handedness
114 }
115 else
116 {
117 // Default tangent when not present (will be computed or ignored)
118 vertex.tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
119 }
120
121 if ( vertexData.uvDataView.has_value() )
122 {
123 vertex.textureCoordinate = {
124 vertexData.uvDataView->data[2 * vertexIndex + 0],
125 vertexData.uvDataView->data[2 * vertexIndex + 1],
126 };
127 }
128 if ( vertexData.lightmapDataView.has_value() )
129 {
130 vertex.lightmapUvCoordinate = {
131 vertexData.lightmapDataView->data[2 * vertexIndex + 0],
132 vertexData.lightmapDataView->data[2 * vertexIndex + 1],
133 };
134 }
135
136 vertices.emplace_back( vertex );
137 indices.emplace_back( static_cast<uint32_t>(vertices.size()) - 1 ); // local vertex index
138 }
139 return { vertices, indices };
140 }
141
143 return image != tinygltf::Image();
144 }
145
146 GltfLoader::VulkanSchneeExtension::VulkanSchneeExtension(const tinygltf::Value &vs_extension) {
147 const std::string version = extractVersion(vs_extension);
148
149 // Parse mesh properties
150 if (vs_extension.Has("meshProperties")) {
151 meshProperties = MeshProperties(vs_extension);
152 PLOGI << "IsStatic: " << meshProperties->isStatic << " CollisionEnabled: " << meshProperties->collisionEnabled;
153 }
154
155 // Parse lighting properties (note: key is "lightingProperties" not "lightProperties")
156 if (vs_extension.Has("lightingProperties")) {
157 lightProperties = LightProperties(vs_extension);
158 PLOGI << "HasLightmaps: " << lightProperties->getHasLightmaps();
159 }
160
161 // Parse lightmap properties (at root level, sibling of lightingProperties)
162 if (vs_extension.Has("lightmapProperties")) {
163 lightmapProperties = LightProperties::LightmapProperties(vs_extension.Get("lightmapProperties"));
164 PLOGI << "LightmapPath: " << lightmapProperties->lightmapPath;
165 }
166 }
167
169 const tinygltf::Value &meshProperties = extension.Get("meshProperties");
170
171 const tinygltf::Value &isStatic = meshProperties.Get("isStatic");
172 const tinygltf::Value &collisionEnabled = meshProperties.Get("collisionEnabled");
173
174 this->isStatic = isStatic.Get<bool>();
175 this->collisionEnabled = collisionEnabled.Get<bool>();
176 }
177
179 // Note: key is "lightingProperties" not "lightProperties"
180 const tinygltf::Value &lightingProperties = extension.Get("lightingProperties");
181
182 const tinygltf::Value &castShadowsVal = lightingProperties.Get("castShadows");
183 const tinygltf::Value &receiveShadowsVal = lightingProperties.Get("receiveShadows");
184 const tinygltf::Value &hasLightmapsVal = lightingProperties.Get("hasLightmaps");
185
186 this->castShadows = castShadowsVal.Get<bool>();
187 this->receiveShadows = receiveShadowsVal.Get<bool>();
188 this->hasLightmaps = hasLightmapsVal.Get<bool>();
189
190 // Note: lightmapProperties is now parsed at VulkanSchneeExtension level as a sibling
191 }
192
194 const tinygltf::Value &lightmapProps) {
195 // Now receiving the lightmapProperties object directly (not its parent)
196
197 if (lightmapProps.Has("lightmapPath")) {
198 const tinygltf::Value &pathVal = lightmapProps.Get("lightmapPath");
199 if (pathVal.IsString()) {
200 this->lightmapPath = pathVal.Get<std::string>();
201 } else {
202 PLOGE << "Lightmap path is not a string. Will return empty string.";
203 }
204 } else {
205 PLOGE << "Lightmap path not found in lightmapProperties. Will return empty string.";
206 }
207
208 if (lightmapProps.Has("lightmapUvIndex")) {
209 const tinygltf::Value &uvIndexVal = lightmapProps.Get("lightmapUvIndex");
210 if (uvIndexVal.IsInt()) {
211 this->lightmapUvIndex = static_cast<uint32_t>(uvIndexVal.Get<int>());
212 } else {
213 PLOGE << "Lightmap UV index is not an integer. Will return 0.";
214 }
215 } else {
216 PLOGE << "Lightmap UV index not found in lightmapProperties. Will return 0.";
217 }
218 }
219
223
227
231
235
236 std::string GltfLoader::VulkanSchneeExtension::extractVersion(const tinygltf::Value &vs_extension) {
237 if (!vs_extension.Has("version")) {
238 PLOGE << "Version not found in material extension. Will return empty string.";
239 return "";
240 }
241 return vs_extension.Get("version").Get<std::string>();
242 }
243
245 this->type = param.Get("type").Get<std::string>();
246 this->socketType = param.Get("socketType").Get<std::string>();
247
248 // Only extract value if this is a value parameter (not connected)
249 if (this->type == "value" && param.Has("value")) {
250 this->value = param.Get("value");
251 }
252 }
253
255 // Extract shader type
256 if (vs_extension.Has("shaderType")) {
257 this->shaderType = vs_extension.Get("shaderType").Get<std::string>();
258 } else {
259 PLOGE << "Shader type not found in material extension. Will default to Normals shader";
261 }
263
264
265 PLOGI << "Loading material extension - Shader Type: " << this->shaderType;
266
267 // Extract shader parameters
268 if (vs_extension.Has("shaderParameters")) {
269 const tinygltf::Value &params = vs_extension.Get("shaderParameters");
270 if (params.IsObject()) {
271 const auto &paramsObj = params.Get<tinygltf::Value::Object>();
272 for (const auto &[key, value]: paramsObj) {
273 this->shaderParameters.emplace(key, ShaderParameter(value));
274 }
275 PLOGI << " Shader Parameters: " << this->shaderParameters.size() << " parameter(s) loaded";
276 for (const auto &[key, param]: this->shaderParameters) {
277 PLOGI << " - " << key << " [" << param.socketType << "]: " << param.type;
278 }
279 }
280 }
281
282 // Extract node properties
283 if (vs_extension.Has("nodeProperties")) {
284 const tinygltf::Value &nodeProps = vs_extension.Get("nodeProperties");
285 if (nodeProps.IsObject()) {
286 const auto &propsObj = nodeProps.Get<tinygltf::Value::Object>();
287 for (const auto &[key, value]: propsObj) {
288 // Node properties can be int or float, convert to double
289 if (value.IsInt()) {
290 this->nodeProperties[key] = static_cast<double>(value.Get<int>());
291 } else if (value.IsReal()) {
292 this->nodeProperties[key] = value.Get<double>();
293 }
294 }
295 PLOGI << " Node Properties: " << this->nodeProperties.size() << " property(ies) loaded";
296 for (const auto &[key, val]: this->nodeProperties) {
297 PLOGI << " - " << key << ": " << val;
298 }
299 }
300 }
301
302 // Extract custom properties (optional)
303 if (vs_extension.Has("customProperties")) {
304 const tinygltf::Value &customProps = vs_extension.Get("customProperties");
305 if (customProps.IsObject()) {
306 const auto &customObj = customProps.Get<tinygltf::Value::Object>();
307 for (const auto &[key, value]: customObj) {
308 this->customProperties[key] = value;
309 }
310 PLOGI << " Custom Properties: " << this->customProperties.size() << " property(ies) loaded";
311 }
312 }
313 }
314
315 GltfLoader::MaterialExtensions::MaterialExtensions(const tinygltf::ExtensionMap &material) {
316 if (material.find("VULKAN_SCHNEE_materials") != material.end()) {
317 this->vulkanSchneeMaterialExtension = VulkanSchneeMaterialExtension(material.at("VULKAN_SCHNEE_materials"));
318 } else {
319 PLOGI << " No VULKAN_SCHNEE_materials extension found in material";
320 }
321 }
322
324 return this->lightProperties.has_value();
325 }
326
328 return this->lightmapProperties.has_value();
329 }
330
332 return this->meshProperties.has_value();
333 }
334
335 GltfLoader::StaticMeshExtensions::StaticMeshExtensions(const tinygltf::ExtensionMap &extensions) {
336 if (extensions.find("VULKAN_SCHNEE_engine_properties") != extensions.end()) {
337 this->vulkanSchneeExtension = VulkanSchneeExtension(extensions.at("VULKAN_SCHNEE_engine_properties"));
338 } else {
339 PLOGI << " No VULKAN_SCHNEE_engine_properties extension found in mesh";
340 }
341 }
342
343 GltfLoader::GltfMaterialData::GltfMaterialData(const tinygltf::Model &model, const tinygltf::Material &material,
344 const StaticMeshExtensions &meshSettings) : material(material) {
345 TRACY_ZONE_SCOPED_NAMED("Loading material data");
346
347 PLOGI << "Loading material: " << material.name;
348
349 if (!material.extensions.empty()) {
351 PLOGI << " Material has Vulkan Schnee extension";
352 } else {
353 PLOGI << " Material has no extensions, using default shader";
354 }
355
356 // Parse standard glTF PBR data
357 parsePbrData(model, material);
358
359 // Initialize baseColorFactor BEFORE setupMaterialData() so it's available as a fallback
360 const std::vector<double> baseColor = material.pbrMetallicRoughness.baseColorFactor;
361 if (!baseColor.empty()) {
362 if (baseColor.size() < 4)
363 throw std::runtime_error("Tried to load a color which was not rgba!");
364 baseColorFactor.r = static_cast<float>(baseColor[0]);
365 baseColorFactor.g = static_cast<float>(baseColor[1]);
366 baseColorFactor.b = static_cast<float>(baseColor[2]);
367 baseColorFactor.a = static_cast<float>(baseColor[3]);
368 } else {
369 baseColorFactor = glm::vec4(1.0f); // white as default when there is no color specified
370 }
371
372 // Initialize textureIndex BEFORE setupMaterialData() so it's available for material data creation
373 if (meshSettings.vulkanSchneeExtension.has_value()) {
374 const auto &vulkanSchneeExtension = meshSettings.vulkanSchneeExtension.value();
375
376 if (vulkanSchneeExtension.hasLightmapProperties()
377 && vulkanSchneeExtension.lightProperties.value().getHasLightmaps()
378 && vulkanSchneeExtension.lightmapProperties.value().isLightmapPathValid()
379 ) {
380 const auto &lightMapSettings = vulkanSchneeExtension.lightmapProperties.value();
381
382 // Store the lightmap path for later resolution to descriptor index
383 lightmapPath = lightMapSettings.lightmapPath;
384
385 // Load lightmap texture data from EXR file
386 ExrLoader exrLoader = {};
387 lightmapTextureData = GltfTextureData(exrLoader.load(lightMapSettings.lightmapPath));
388 PLOGI << "Loaded lightmap texture from: " << lightmapPath;
389
390 // Also keep the old behavior for backwards compatibility (textureData stores lightmap)
392 textureIndex = std::numeric_limits<int>::max();
393 } else {
394 const tinygltf::TextureInfo &textureInfo = material.pbrMetallicRoughness.baseColorTexture;
395 if (textureInfo.index != -1) {
396 const tinygltf::Texture &texture = model.textures[textureInfo.index];
397 const tinygltf::Image &image = model.images[texture.source];
398
400 textureIndex = textureInfo.index;
401 }
402 }
403 } else {
404 // No vulkan schnee extension - still try to get base color texture
405 const tinygltf::TextureInfo &textureInfo = material.pbrMetallicRoughness.baseColorTexture;
406 if (textureInfo.index != -1) {
407 const tinygltf::Texture &texture = model.textures[textureInfo.index];
408 const tinygltf::Image &image = model.images[texture.source];
409
411 textureIndex = textureInfo.index;
412 }
413 }
414
416
417 if (!meshSettings.vulkanSchneeExtension.has_value()) {
418 PLOGI << "Mesh has no vulkan schnee extension in exported mesh data";
419 return;
420 }
421 }
422
423 void GltfLoader::GltfMaterialData::parsePbrData(const tinygltf::Model &model, const tinygltf::Material &material) {
424 // Parse metallic/roughness factors
425 roughnessFactor = static_cast<float>(material.pbrMetallicRoughness.roughnessFactor);
426 metallicFactor = static_cast<float>(material.pbrMetallicRoughness.metallicFactor);
427
428 // Parse metallic/roughness texture - extract image data for loading into descriptor array
429 const auto& metallicRoughnessTexture = material.pbrMetallicRoughness.metallicRoughnessTexture;
430 if (metallicRoughnessTexture.index != -1) {
431 gltfMetallicRoughnessTextureIndex = metallicRoughnessTexture.index;
432 const tinygltf::Texture& texture = model.textures[metallicRoughnessTexture.index];
433 if (texture.source >= 0 && texture.source < static_cast<int>(model.images.size())) {
434 metallicRoughnessTextureData = GltfTextureData(model.images[texture.source]);
435 PLOGD << " Metallic/roughness texture extracted (GLTF index " << metallicRoughnessTexture.index
436 << ", image source " << texture.source << ")";
437 }
438 }
439
440 // Parse normal texture - extract image data for loading into descriptor array
441 if (material.normalTexture.index != -1) {
442 gltfNormalTextureIndex = material.normalTexture.index;
443 normalScale = static_cast<float>(material.normalTexture.scale);
444 const tinygltf::Texture& texture = model.textures[material.normalTexture.index];
445 if (texture.source >= 0 && texture.source < static_cast<int>(model.images.size())) {
446 normalTextureData = GltfTextureData(model.images[texture.source]);
447 PLOGD << " Normal texture extracted (GLTF index " << material.normalTexture.index
448 << ", image source " << texture.source << ", scale " << normalScale << ")";
449 }
450 }
451
452 // Parse emissive texture - extract image data for loading into descriptor array
453 if (material.emissiveTexture.index != -1) {
454 gltfEmissiveTextureIndex = material.emissiveTexture.index;
455 const tinygltf::Texture& texture = model.textures[material.emissiveTexture.index];
456 if (texture.source >= 0 && texture.source < static_cast<int>(model.images.size())) {
457 emissiveTextureData = GltfTextureData(model.images[texture.source]);
458 PLOGD << " Emissive texture extracted (GLTF index " << material.emissiveTexture.index
459 << ", image source " << texture.source << ")";
460 }
461 }
462
463 // Parse emissive factor
464 const auto& emissive = material.emissiveFactor;
465 if (emissive.size() >= 3) {
466 emissiveFactor = glm::vec3(
467 static_cast<float>(emissive[0]),
468 static_cast<float>(emissive[1]),
469 static_cast<float>(emissive[2])
470 );
471 if (glm::length(emissiveFactor) > 0.0f) {
472 PLOGD << " Emissive factor: (" << emissiveFactor.x << ", " << emissiveFactor.y << ", " << emissiveFactor.z << ")";
473 }
474 }
475
476 PLOGD << " Metallic: " << metallicFactor << ", Roughness: " << roughnessFactor;
477 }
478
480 return textureIndex != std::numeric_limits<uint32_t>::max();
481 }
482
486
490
492 return materialExtensions.has_value() &&
493 materialExtensions->vulkanSchneeMaterialExtension.has_value();
494 }
495
498 throw std::runtime_error("Material does not have Vulkan Schnee extension");
499 }
500 return materialExtensions->vulkanSchneeMaterialExtension.value();
501 }
502
505 const std::string shaderName = getVulkanSchneeExtension().shaderType;
506 return shaderName;
507 }
508 return "diffuseFlatColor"; // Default shader
509 }
510
516
518 if (!materialExtensions.has_value()) {
519 PLOGI << "setupMaterialData: No material extensions, defaulting to NormalMaterialData";
521 return;
522 }
523
524 if (!materialExtensions->vulkanSchneeMaterialExtension.has_value()) {
525 PLOGI << "setupMaterialData: No vulkan schnee material extension, defaulting to NormalMaterialData";
527 return;
528 }
529
530 const VulkanSchneeMaterialExtension &ext = materialExtensions->vulkanSchneeMaterialExtension.value();
531 PLOGI << "setupMaterialData: shaderType=" << ext.shaderType
532 << ", materialName=" << static_cast<int>(ext.materialName)
533 << " (STATIC_LIGHTMAP=" << static_cast<int>(PipelineNames::STATIC_LIGHTMAP) << ")";
534
535#define MATCH_MATERIAL(name, type, ...) \
536 if (ext.materialName == PipelineNames::name) { \
537 materialData = type(__VA_ARGS__); \
538 return; \
539 }
540
541 auto shaderParams = materialExtensions->vulkanSchneeMaterialExtension->shaderParameters;
542
543 const auto it = shaderParams.find("Color");
544 std::array<float, 4> colors{};
545 if (it != shaderParams.end()) {
546 const auto arr = it->second.value.Get<tinygltf::Value::Array>();
547 colors = {
548 static_cast<float>(arr[0].GetNumberAsDouble()),
549 static_cast<float>(arr[1].GetNumberAsDouble()),
550 static_cast<float>(arr[2].GetNumberAsDouble()),
551 static_cast<float>(arr[3].GetNumberAsDouble())
552 };
553 }
554
555
557
558
560 glm::vec3 diffuseColor;
561 // Use "Color" shader parameter if available and non-zero, otherwise fall back to PBR baseColorFactor
562 bool colorParamValid = (it != shaderParams.end()) && (colors[0] != 0.0f || colors[1] != 0.0f || colors[2] != 0.0f);
563 if (colorParamValid) {
564 diffuseColor = {colors[0], colors[1], colors[2]};
565 PLOGI << "FLAT_COLOR using shader Color param: " << diffuseColor.r << ", " << diffuseColor.g << ", " << diffuseColor.b;
566 } else {
567 // Use PBR baseColorFactor as fallback (typically from glTF material's pbrMetallicRoughness)
568 diffuseColor = glm::vec3(baseColorFactor);
569 PLOGI << "FLAT_COLOR using baseColorFactor fallback: " << diffuseColor.r << ", " << diffuseColor.g << ", " << diffuseColor.b
570 << " (Color param " << (it != shaderParams.end() ? "was zero" : "not found") << ")";
571 }
573 return;
574 };
575
576 const uint32_t texIndex = getTextureIndex();
577 const glm::vec4 colorFactor = baseColorFactor;
578
579 // Extract properties with defaults
580 float shScale = 0.15f;
581 float ambientTerm = 0.1f;
582 uint32_t debugMode = 0;
583 uint32_t toneMapping = 0;
584
585 if (ext.nodeProperties.count("sh_scale")) shScale = static_cast<float>(ext.nodeProperties.at("sh_scale"));
586 if (ext.nodeProperties.count("ambient_term")) ambientTerm = static_cast<float>(ext.nodeProperties.at("ambient_term"));
587 if (ext.nodeProperties.count("debug_mode")) debugMode = static_cast<uint32_t>(ext.nodeProperties.at("debug_mode"));
588 if (ext.nodeProperties.count("tone_mapping")) {
589 // Map string enum back if it was stored as string, but here it's double
590 toneMapping = static_cast<uint32_t>(ext.nodeProperties.at("tone_mapping"));
591 }
592
594 MATCH_MATERIAL(L0_SHADER, L0ShaderMaterialData, texIndex, colorFactor, shScale, ambientTerm);
595 MATCH_MATERIAL(L1_SHADER, L1ShaderMaterialData, texIndex, colorFactor, shScale, ambientTerm);
596 MATCH_MATERIAL(L2_SHADER, L2ShaderMaterialData, texIndex, colorFactor, shScale, ambientTerm);
597 MATCH_MATERIAL(MOVABLE_DIFFUSE_SHADER, MovableDiffuseShaderMaterialData, texIndex, colorFactor, shScale, ambientTerm, debugMode, toneMapping);
598 MATCH_MATERIAL(DYNAMIC_TEXTURES, DynamicTexturesMaterialData, texIndex, colorFactor, shScale, ambientTerm, debugMode, toneMapping);
599
600 // Static lightmap material - uses PBR textures and lightmap
602 // For static lightmap, default ambient_term to 1.0 (full brightness) if not specified
603 // This ensures materials without lightmaps appear fully lit rather than dark
604 float lightmapAmbientTerm = ext.nodeProperties.count("ambient_term")
605 ? ambientTerm : 1.0f;
606
607 StaticLightmapMaterialData staticLightmapData(
608 texIndex, // base color texture index
609 colorFactor, // base color factor
610 normalTextureIndex, // normal texture index
611 0xFFFFFFFF, // lightmap texture index (set later by AssetManager)
612 roughnessFactor, // roughness factor
613 metallicFactor, // metallic factor
614 normalScale, // normal scale
615 emissiveFactor, // emissive factor
616 shScale, // SH scale
617 lightmapAmbientTerm, // ambient term (defaults to 1.0 for lightmap materials)
618 debugMode, // debug mode
619 toneMapping // tone mapping
620 );
621 materialData = staticLightmapData;
622 PLOGI << "Created StaticLightmapMaterialData with baseColorTex=" << texIndex
623 << " (normalTex not loaded - PBR texture loading not yet implemented)";
624 return;
625 }
626
627 // If we reach here, no material type matched - default to NormalMaterialData
628 PLOGW << "setupMaterialData: No material type matched for materialName="
629 << static_cast<int>(ext.materialName) << ", shaderType=" << ext.shaderType
630 << ". Defaulting to NormalMaterialData.";
632
633#undef MATCH_MATERIAL
634 }
635
636 GltfLoader::GltfMeshPrimitiveData::GltfMeshPrimitiveData(tinygltf::Model model, tinygltf::Primitive primitive,
637 StaticMeshExtensions meshSettings) {
638 TRACY_ZONE_SCOPED_NAMED("Loading primitive data");
639 if (primitive.material != -1) {
640 // the primitive has a material
641 materialData = GltfMaterialData(model, model.materials[primitive.material], meshSettings);
642 }
643
644 Vertex vertex;
645
646 int uvCoordinateAccessorIndex;
647 if (auto &vkScExtension = meshSettings.vulkanSchneeExtension;
648 vkScExtension.has_value()
649 && vkScExtension->hasLightProperties()
650 && vkScExtension->lightProperties->getHasLightmaps()
651 && vkScExtension->hasLightmapProperties()) {
652 const uint32_t lightmapIndex = meshSettings.vulkanSchneeExtension.value().lightmapProperties.value().
653 lightmapUvIndex;
654 std::stringstream strStream;
655 strStream << "TEXCOORD_" << lightmapIndex;
656 uvCoordinateAccessorIndex = primitive.attributes.find(strStream.str())->second;
657 } else {
658 uvCoordinateAccessorIndex = primitive.attributes.find("TEXCOORD_0")->second;
659 }
660
661 if (!(model.accessors.size() > static_cast<size_t>(uvCoordinateAccessorIndex) && uvCoordinateAccessorIndex != -
662 1)) {
663 PLOGE << "Failed to load UV coordinate accessor";
664 }
665 const int &positionAccessorIndex = primitive.attributes.find("POSITION")->second;
666 if (!(model.accessors.size() > static_cast<size_t>(positionAccessorIndex) && positionAccessorIndex >= 0)) {
667 PLOGE << "Failed to load position accessor";
668 }
669
670 // ADDED: Check for NORMAL attribute and load it if present
671 bool hasNormals = false;
672 const float *vertexNormals = nullptr;
673 auto normalAttributeIt = primitive.attributes.find("NORMAL");
674 if (normalAttributeIt != primitive.attributes.end()) {
675 int normalAccessorIndex = normalAttributeIt->second;
676 if (model.accessors.size() > static_cast<size_t>(normalAccessorIndex) && normalAccessorIndex >= 0) {
677 const tinygltf::Accessor &normalAccessor = model.accessors[normalAccessorIndex];
678 const tinygltf::BufferView &normalBufferView = model.bufferViews[normalAccessor.bufferView];
679 const tinygltf::Buffer &normalBuffer = model.buffers[normalBufferView.buffer];
680 vertexNormals = reinterpret_cast<const float *>(
681 &normalBuffer.data[normalBufferView.byteOffset + normalAccessor.byteOffset]);
682 hasNormals = true;
683 PLOGI << "NORMAL attribute found and loaded.";
684 } else {
685 PLOGW << "NORMAL attribute found but accessor invalid.";
686 }
687 } else {
688 PLOGW << "NORMAL attribute NOT found. Normals will be (0.0, 0.0, 0.0).";
689 } {
690 const tinygltf::Accessor &accessor = model.accessors[uvCoordinateAccessorIndex];
691 if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT || accessor.type != TINYGLTF_TYPE_VEC2) {
692 PLOGE << "UV coordinates are not in supported format";
693 }
694 const tinygltf::BufferView &bufferView = model.bufferViews[accessor.bufferView];
695 const tinygltf::Buffer &buffer = model.buffers[bufferView.buffer];
696
697 const float *uvData = reinterpret_cast<const float *>(&buffer.data[
698 bufferView.byteOffset + accessor.byteOffset]);
699
700 const tinygltf::Accessor &positionAccessor = model.accessors[positionAccessorIndex];
701 const tinygltf::BufferView &positionBufferView = model.bufferViews[positionAccessor.bufferView];
702 const tinygltf::Buffer &positionBuffer = model.buffers[positionBufferView.buffer];
703 const float *vertexPositions = reinterpret_cast<const float *>(
704 &positionBuffer.data[positionBufferView.byteOffset + positionAccessor.byteOffset]);
705
706 // Get the index accessor.
707 const tinygltf::Accessor &indexAccessor = model.accessors[primitive.indices];
708 const tinygltf::BufferView &indexBufferView = model.bufferViews[indexAccessor.bufferView];
709 const tinygltf::Buffer &indexBuffer = model.buffers[indexBufferView.buffer];
710 const void *indexDataPointer =
711 static_cast<const void *>(&indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]);
712
713 // Loop through indices.
714 for (size_t i = 0u; i < indexAccessor.count; i++) {
715 uint32_t vertexIndex = 0;
716 switch (indexAccessor.componentType) {
717 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
718 vertexIndex = static_cast<uint32_t>(reinterpret_cast<const uint8_t *>(indexDataPointer)[i]);
719 break;
720 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
721 vertexIndex = static_cast<uint32_t>(reinterpret_cast<const uint16_t *>(indexDataPointer)[i]);
722 break;
723 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
724 vertexIndex = reinterpret_cast<const uint32_t *>(indexDataPointer)[i];
725 break;
726 default:
727 PLOGF << "Unsupported index type! Type: " << indexAccessor.componentType;
728 }
729
730 vertex.position = glm::vec4(
731 vertexPositions[3 * vertexIndex + 0], vertexPositions[3 * vertexIndex + 1],
732 vertexPositions[3 * vertexIndex + 2], 0.0f
733 );
734
735 vertex.textureCoordinate = glm::vec2(uvData[2 * vertexIndex + 0], // U component
736 uvData[2 * vertexIndex + 1] // V component
737 );
738
739 // ADDED: Assign normal if available
740 if (hasNormals) {
741 vertex.normal = glm::vec4(
742 vertexNormals[3 * vertexIndex + 0], vertexNormals[3 * vertexIndex + 1],
743 vertexNormals[3 * vertexIndex + 2], 0.0f
744 );
745 } else {
746 vertex.normal = glm::vec4(0.0f); // Default to (0,0,0,0) if no normals
747 }
748
749 vertices.push_back(vertex);
750 indices.push_back(static_cast<uint32_t>(vertices.size() - 1));
751 }
752 }
753 }
754
756 return vertices;
757 }
758
759 std::vector<uint32_t> GltfLoader::GltfMeshPrimitiveData::getIndices() const {
760 return indices;
761 }
762
766
770
774
775 GltfLoader::GltfMeshData::GltfMeshData(const tinygltf::Model &model, const tinygltf::Mesh &mesh,
776 const tinygltf::Node &node, const glm::mat4 &transform) {
777 TRACY_ZONE_SCOPED_NAMED("Loading Mesh data");
778 this->transform = transform;
779 this->mesh = mesh;
780 this->name = node.name;
781
782 const auto extensions = StaticMeshExtensions(node.extensions);
783
784 primitives.resize(this->mesh.primitives.size());
785 for (uint32_t i = 0u; i < this->mesh.primitives.size(); i++) {
786 primitives[i] = GltfMeshPrimitiveData(model, this->mesh.primitives[i], extensions);
787 }
788 }
789
791 const tinygltf::Value &hasLightMap = extras.Get("HasLightmaps");
792 bUsesLightMaps = hasLightMap.Get<bool>();
793
794 const tinygltf::Value &lightMapPathValue = extras.Get("LightMapPath");
795 lightMapPath = std::filesystem::path(lightMapPathValue.Get<std::string>());
796
797 const tinygltf::Value &lightMapUvIndexValue = extras.Get("LightMapUvIndex");
798 lightMapUvIndex = lightMapUvIndexValue.Get<int>();
799 }
800
801 void GltfLoader::load(const std::filesystem::path &path, std::vector<GltfMeshData> &meshes) const {
802
803 tinygltf::Model model = EngineCore::UnpackOptional(loadModel(path));
804
805 const size_t nodeCount = model.nodes.size();
806 std::vector<GltfMeshData> meshList;
807 meshList.reserve(nodeCount);
808
809 TRACY_LOCKABLE(std::mutex, lock, "mesh list mutex");
810
811 for (const tinygltf::Node &node: model.nodes) {
812 TRACY_ZONE_SCOPED_NAMED("Loading Node");
813
814 // Skip nodes without meshes (cameras, lights, empties, armatures)
815 if (node.mesh < 0) {
816 continue;
817 }
818
819 auto transform = Transform();
820 {
821 TRACY_ZONE_SCOPED_NAMED("Loading Mesh Transform");
822 if (!node.translation.empty()) {
823 // x,y,z
824 const auto translation = glm::vec3(node.translation[0], node.translation[1], node.translation[2]);
825 transform.setLocation(translation);
826 }
827
828 if (!node.rotation.empty()) {
829 // x,y,z,w
830 const auto rotation = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1],
831 node.rotation[2]);
832 transform.setRotationQuat(rotation);
833 }
834
835 if (!node.scale.empty()) {
836 const auto scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]);
837 transform.setScale(scale);
838 }
839 }
840
841 const GltfMeshData data(model, model.meshes[node.mesh], node, transform.toMatrix());
842
843 meshes.push_back(data);
844 }
845
846 }
847
848 std::optional<tinygltf::Model> GltfLoader::loadModel(const std::filesystem::path &path) const {
849 // loader for tinygltf
850 TRACY_ZONE_SCOPED_NAMED("Loading Mesh");
851 tinygltf::Model model;
852 tinygltf::TinyGLTF loader;
853 std::string error{};
854 std::string warning{};
855
856 loader.SetStoreOriginalJSONForExtrasAndExtensions(true); {
857 TRACY_ZONE_SCOPED_NAMED("File Read");
858
859 if (!loader.LoadASCIIFromFile(&model, &error, &warning, path.generic_string())) {
860 std::stringstream errorStream{};
861 errorStream << "Failed to load mesh: " << path.generic_string() << " with gltf loading error: " << error;
862 throw std::runtime_error(errorStream.str());
863 }
864 if (!error.empty()) {
865 PLOGE_ASSET << "Failed to load mesh: " << path.generic_string() << " with error: " << error;
866 }
867 if (!warning.empty()) {
868 PLOGE_ASSET << "Loaded asset " << path.generic_string() << " with warnings " << warning;
869 }
870 }
871 return model;
872 }
873
874 tinygltf::Model GltfLoader::loadAsync(const std::filesystem::path &path) const {
875 TRACY_ZONE_SCOPED_NAMED("Loading Mesh Async (Deferred)");
876
877 tinygltf::TinyGLTF loader;
878
879 // Set a no-op image loader to skip actual image file loading
880 // This allows parsing image metadata from JSON without loading image files from disk
881 auto noOpImageLoader = [](tinygltf::Image* image, const int image_idx, std::string* err,
882 std::string* warn, int width, int height,
883 const unsigned char* data, int size, void* user_data) -> bool {
884 //PLOGI << "Skipping image load for image " << image_idx << " (deferred mode)";
885 return true; // Return true to indicate success without actually loading
886 };
887 loader.SetImageLoader(noOpImageLoader, nullptr);
888 loader.SetStoreOriginalJSONForExtrasAndExtensions(true);
889
890 tinygltf::Model model;
891 std::string error, warning;
892 if (!loader.LoadASCIIFromFile(&model, &error, &warning, path.generic_string())) {
893 PLOGE << "Failed to load mesh: " << path.generic_string();
894 }
895 if (!error.empty()) {
896 PLOGE << "Error loading mesh: " << error;
897 }
898 if (!warning.empty()) {
899 PLOGW << "Warning loading mesh: " << warning;
900 }
901
902 return model;
903 }
904
905 tinygltf::Model GltfLoader::loadMetadataOnly(const std::filesystem::path &path) const {
906 TRACY_ZONE_SCOPED_NAMED("Loading GLTF Metadata Only");
907
908 tinygltf::TinyGLTF loader;
909
910 // Only parse nodes (names and transforms) - skip everything else
911 loader.SetParseStages(tinygltf::PARSE_NODES_ONLY);
912
913 tinygltf::Model model;
914 std::string error, warning;
915
916 const std::string pathStr = path.generic_string();
917 const std::string ext = path.extension().string();
918
919 bool success = false;
920 if (ext == ".glb" || ext == ".GLB") {
921 success = loader.LoadBinaryFromFile(&model, &error, &warning, pathStr);
922 } else {
923 success = loader.LoadASCIIFromFile(&model, &error, &warning, pathStr);
924 }
925
926 if (!success) {
927 PLOGE << "Failed to load GLTF metadata: " << path.generic_string();
928 }
929 if (!error.empty()) {
930 PLOGE << "Error loading GLTF metadata: " << error;
931 }
932 if (!warning.empty()) {
933 PLOGW << "Warning loading GLTF metadata: " << warning;
934 }
935
936 return model;
937 }
938}
#define EXTRACT_VERTEX_DATA(view, attribKey)
#define MATCH_MATERIAL(name, type,...)
constexpr EngineCore::PipelineNames DEFAULT_MATERIAL_PIPELINE
Definition Settings.h:59
#define TRACY_LOCKABLE(type, varname, desc)
#define TRACY_ZONE_SCOPED_NAMED(name)
Material data for an Object which displays a flat color.
Dynamic textures material with PBR and lightmap support.
Loader for EXR (OpenEXR) image files.
Definition ExrLoader.h:81
tinygltf::Image load(const std::filesystem::path &filename)
Load a complete EXR image file.
Definition ExrLoader.cpp:40
tinygltf::Model loadMetadataOnly(const std::filesystem::path &path) const
Loads only the GLTF metadata (nodes, names, transforms) without loading buffer data.
void load(const std::filesystem::path &path, std::vector< GltfMeshData > &meshes) const
Loads a vector of meshes from a gltf file.
std::optional< tinygltf::Model > loadModel(const std::filesystem::path &path) const
tinygltf::Model loadAsync(const std::filesystem::path &path) const
NamedThreadPool * threadPool
Definition GltfLoader.h:471
Ecs::PrimitiveData loadPrimitiveData(GltfVertexData vertexData)
GltfLoader(NamedThreadPool *threadPool=nullptr)
std::vector< Vertex > getPrimitiveVertices(const tinygltf::Primitive &primitive)
L1 Spherical Harmoics shader.
Moveable diffuse shader with PBR and lightmap support.
Material data for an object which displays its normals.
Static lightmap material with PBR support.
Tracy-named thread pool using BS_tracy::tracy_thread_pool.
Log category system implementation.
T UnpackOptional(std::optional< T > var)
Definition Optional.h:7
@ MOVABLE_DIFFUSE_SHADER
const tinygltf::Accessor & accessor
Definition GltfLoader.h:36
const tinygltf::BufferView & bufferView
Definition GltfLoader.h:37
GltfBufferDataView(const std::vector< tinygltf::Buffer > &buffers, const std::vector< tinygltf::BufferView > &bufferViews, const tinygltf::Accessor &accessor)
Stores the material data which is relevant for our current set of shaders.
Definition GltfLoader.h:232
void parsePbrData(const tinygltf::Model &model, const tinygltf::Material &material)
const VulkanSchneeMaterialExtension & getVulkanSchneeExtension() const
std::variant< NormalMaterialData, DiffuseFlatColorMaterialData, DiffuseShaderMaterialData, MovableDiffuseShaderMaterialData, L0ShaderMaterialData, L1ShaderMaterialData, L2ShaderMaterialData, DynamicTexturesMaterialData, StaticLightmapMaterialData > materialData
Definition GltfLoader.h:308
std::variant< NormalMaterialData, DiffuseFlatColorMaterialData, DiffuseShaderMaterialData, MovableDiffuseShaderMaterialData, L0ShaderMaterialData, L1ShaderMaterialData, L2ShaderMaterialData, DynamicTexturesMaterialData, StaticLightmapMaterialData > getMaterialDataRaw() const
std::optional< MaterialExtensions > materialExtensions
Definition GltfLoader.h:310
GltfMeshData(const tinygltf::Model &model, const tinygltf::Mesh &mesh, const tinygltf::Node &node, const glm::mat4 &transform)
std::vector< GltfMeshPrimitiveData > primitives
Definition GltfLoader.h:434
Stores intermediate data from loading the asset from disk. These are the vertices,...
Definition GltfLoader.h:350
std::vector< uint32_t > getIndices() const
Gets the raw unprocessed list of all indices.
std::vector< Vertex > getVertices() const
Gets a list of all unprocessed vertices of this primtitive.
Texture * getColorTexturePtr() const
Gets the pointer to the primitive of the color texture. This is only a valid pointer after it has bee...
std::optional< GltfBufferDataView > positionDataView
Definition GltfLoader.h:46
GltfVertexData(const std::vector< tinygltf::Buffer > &buffers, const std::vector< tinygltf::BufferView > &bufferViews, const std::vector< tinygltf::Accessor > &accessors, const tinygltf::Primitive &primitive)
std::optional< GltfBufferDataView > normalDataView
Definition GltfLoader.h:47
std::optional< GltfBufferDataView > indexDataView
Definition GltfLoader.h:51
std::optional< GltfBufferDataView > lightmapDataView
Definition GltfLoader.h:50
std::optional< GltfBufferDataView > uvDataView
Definition GltfLoader.h:49
std::optional< GltfBufferDataView > tangentDataView
Definition GltfLoader.h:48
Stores all material extensions. So if another extension for some kind of specific material attribute ...
Definition GltfLoader.h:202
std::optional< VulkanSchneeMaterialExtension > vulkanSchneeMaterialExtension
Definition GltfLoader.h:207
Parses all extensions and extracts recognized extensions.
Definition GltfLoader.h:217
std::optional< VulkanSchneeExtension > vulkanSchneeExtension
Definition GltfLoader.h:221
stores all properties related to lighting information
Definition GltfLoader.h:112
Stores the mesh information like if it is a static object and if it simulates physics.
Definition GltfLoader.h:100
The vulkan schnee extension for gltf files stores all data related to the engines core functionality.
Definition GltfLoader.h:89
std::optional< LightProperties > lightProperties
Definition GltfLoader.h:141
std::optional< MeshProperties > meshProperties
Definition GltfLoader.h:142
static std::string extractVersion(const tinygltf::Value &vs_extension)
Extracts the version from the material extension.
std::optional< LightProperties::LightmapProperties > lightmapProperties
Definition GltfLoader.h:143
Represents a single shader parameter that can be either connected or a direct value.
Definition GltfLoader.h:180
Extracts all material data from the gltf file. All data during runtime will be read from an object of...
Definition GltfLoader.h:169
std::map< std::string, double > nodeProperties
Definition GltfLoader.h:192
std::map< std::string, tinygltf::Value > customProperties
Definition GltfLoader.h:193
std::map< std::string, ShaderParameter > shaderParameters
Definition GltfLoader.h:191
static std::string getPipelineNameFromString(PipelineNames pipelineName)
Gets the pipeline name as string.
The fundamental building block of all meshes in this engine.
Definition Vertex.h:15
glm::vec2 lightmapUvCoordinate
Definition Vertex.h:23
glm::vec4 normal
Definition Vertex.h:20
glm::vec4 tangent
Definition Vertex.h:21
glm::vec4 position
Definition Vertex.h:19
glm::vec2 textureCoordinate
Definition Vertex.h:22